Python异步:子进程间命名管道双向通信问题求助
使用命名管道与pygls子进程实现双向通信的问题解决
问题分析
你遇到的OSError: [Errno 6] Device not configured错误,核心原因有两点:
- 非阻塞模式打开FIFO的时序问题:在非阻塞模式下打开FIFO写端时,如果没有任何进程同时打开该FIFO的读端,系统会抛出此错误。你的主程序在启动子进程前就尝试打开写端FIFO,此时子进程尚未启动,自然没有进程打开对应的读端。
- FIFO读写方向完全混乱:主程序和子进程对同一个FIFO的读写方向完全一致(都读同一个FIFO、都写同一个FIFO),导致数据根本无法双向流通。
修正方案
1. 明确FIFO的通信方向
重新定义两个FIFO的职责:
fifo_main_to_server:主程序写数据,子进程读数据(作为子进程的输入)fifo_server_to_main:子进程写数据,主程序读数据(作为子进程的输出)
2. 调整FIFO打开顺序
先启动子进程,让子进程先打开对应的FIFO端;或者使用阻塞模式打开FIFO,让系统自动等待另一端连接(更稳定)。
3. 修正后的代码示例
主程序代码
import os import sys import asyncio import logging logger = logging.getLogger(__name__) # 明确两个FIFO的职责 fifo_main_to_server = "/tmp/fifo_main_to_server" fifo_server_to_main = "/tmp/fifo_server_to_main" # 创建FIFO(不存在则创建) for fifo_name in [fifo_main_to_server, fifo_server_to_main]: if not os.path.exists(fifo_name): try: os.mkfifo(fifo_name) except OSError as e: logger.error(f"创建FIFO失败 {fifo_name}: {e}") raise e async def main(): # 先启动子进程,确保子进程有机会打开FIFO process = await asyncio.create_subprocess_exec( sys.executable, 'lsp/lsp_server.py', fifo_main_to_server, fifo_server_to_main, stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE ) # 用阻塞模式打开FIFO,系统会自动等待另一端连接 try: # 主程序读子进程输出:打开fifo_server_to_main为只读 fd_server_to_main = os.open(fifo_server_to_main, os.O_RDONLY) # 主程序写子进程输入:打开fifo_main_to_server为只写 fd_main_to_server = os.open(fifo_main_to_server, os.O_WRONLY) except OSError as e: logger.error(f"打开FIFO失败: {e}") raise e # 转换为文件对象,无缓冲确保实时通信 main_read = os.fdopen(fd_server_to_main, 'rb', buffering=0) main_write = os.fdopen(fd_main_to_server, 'wb', buffering=0) # 示例:向子进程发送LSP初始化请求(根据实际业务调整) # main_write.write(b'{"jsonrpc":"2.0","id":1,"method":"initialize","params":{}}\n') # main_write.flush() # 等待子进程结束(根据业务逻辑调整) await process.wait() # 清理资源 main_read.close() main_write.close() # 删除FIFO(可选,按需保留) for fifo_name in [fifo_main_to_server, fifo_server_to_main]: if os.path.exists(fifo_name): os.unlink(fifo_name) if __name__ == "__main__": asyncio.run(main())
lsp_server.py代码
import os import sys from contextlib import contextmanager from pygls.server import LanguageServer server = LanguageServer() @contextmanager def open_fifo(name, mode): # 用阻塞模式打开,避免无对应端的错误 fd = os.open(name, mode) try: if mode & os.O_RDONLY: # 二进制无缓冲模式,适配LSP的JSON数据流 file_obj = os.fdopen(fd, 'rb', buffering=0) elif mode & os.O_WRONLY: file_obj = os.fdopen(fd, 'wb', buffering=0) else: raise ValueError("不支持的打开模式") yield file_obj finally: file_obj.close() if __name__ == "__main__": if len(sys.argv) != 3: print("使用方式: python lsp_server.py <fifo_main_to_server> <fifo_server_to_main>") sys.exit(1) fifo_main_to_server = sys.argv[1] # 子进程的输入流(读主程序发来的数据) fifo_server_to_main = sys.argv[2] # 子进程的输出流(写数据给主程序) with open_fifo(fifo_main_to_server, os.O_RDONLY) as server_stdin, \ open_fifo(fifo_server_to_main, os.O_WRONLY) as server_stdout: print("LSP服务器已启动", flush=True) # start_io第一个参数是输入流,第二个是输出流,对应LSP的请求读取和响应写入 server.start_io(server_stdin, server_stdout)
额外注意事项
- 避免非阻塞模式滥用:除非有明确的异步处理需求,否则阻塞模式更稳定,能自动处理FIFO的两端连接时序问题。
- 编码一致性:pygls默认使用UTF-8编码处理LSP数据,如果使用文本模式打开FIFO,需指定
encoding='utf-8'。 - 资源清理:务必在程序结束后删除FIFO文件,避免残留文件影响后续运行。
内容的提问来源于stack exchange,提问作者ohss117
相关产品推荐
相关产品推荐

