Windows下Rust启动Python子进程时stdin读取阻塞问题求助
最小可复现示例
我准备了可克隆运行的最小复现示例用于测试,完整代码将在本文后续部分提供。
问题简述
在Windows系统中通过Rust启动Python进程时,读取stdin会导致Python子进程无法正常启动更多的Python子进程,Linux下无此问题。添加sleep(1)能临时让子进程正常启动,但如果子进程启动耗时超过1秒则失效,耗时不足1秒又会额外等待,因此sleep只是临时补丁,需要寻求可靠的解决方案。
问题详述
触发场景及背景
该问题仅在Windows环境下由Rust启动Python时出现,直接运行Python程序一切正常。我的应用是基于Tauri的Rust GUI程序,由于存在一个无法移植到Rust(且大概率永远不会移植)的大型Python专属库,因此引入了Python子组件。
这意味着在生成子进程期间,主Rust进程无法处理其他无关命令,因为此时它并未读取stdin。实际程序中我通过多线程启动新线程,理论上生成子进程的同时可以读取stdin,但现在必须避免在子进程生成期间读取stdin,增加了不必要的复杂度。
预期与实际行为差异
预期中say-hi.py应持续输出"Hello World",但实际子进程虽已生成却未开始执行。我确定该问题与stdin相关——程序通过stdin和stdout实现主Rust进程与Python进程间的通信。
主线程读取stdin会以某种方式阻止子进程完全启动。若生成子进程时设置shell=True,则在主循环结束后"Hello World"才会持续输出,且无法停止。
另外,在生成子进程后立即添加sleep(1)可暂时解决问题,但这绝非理想方案。我已在程序中临时使用sleep(3),但如果需要生成数十个子进程,总耗时会过长,且仍存在子进程启动耗时超过3秒导致失效的可能。
问题代码(仅Windows下出现!)
以下代码移除了import语句等无关内容,完整逻辑可参考完整示例。
main.rs
// Setup command let mut command = Command::new("python"); command.args(["-u", "-c", "from main import start; start()"]); command.current_dir(std::env::current_dir()?.join("python")); // Pipe stdin so we can issue commands to tell Python what we want it to do command.stdin(Stdio::piped()); // Spawn the process & take ownership of stdin let mut child = command.spawn()?; let mut stdin = child.stdin.take().unwrap(); // Give a second to make 100% sure Python has started sleep(Duration::from_secs(1)); // Issue start command to Python, wait 5 seconds then tell it to stop println!("Writing start"); stdin.write_all(b"start | ")?; sleep(Duration::from_secs(5)); println!("Writing stop"); stdin.write_all(b"stop | ")?; // Make sure the process has exited before we exit child.wait()?;
main.py
def start(): procs = [] while True: print("Listening for new command...") command = sys.stdin.readline() params = command.split(" | ") print(f"Got command: {command}") if params[0] == "stop": print("Stopping...") # Break from loop break elif params[0] == "start": print("Starting...") # Spawn the subprocess proc = subprocess.Popen([sys.executable, "say-hi.py"]) # Add the subprocess to the list so we can kill it later procs.append(proc) print("Shutting down") # Terminate processes for proc in procs: proc.terminate()
say-hi.py
while True: print(f"Hello world!", flush=True) sleep(1)
运行步骤
以下步骤与示例仓库中的说明一致:
根目录下
- 运行
cargo r,无任何输出 - 修改
python/main.py中的Popen([sys.executable, "say-hi.py"])为Popen([sys.executable, "say-hi.py"], shell=True) - 运行
cargo r,发出stop命令后"Hello world"会持续输出且无法停止
python目录下
shell设为True或False均不影响- 运行
python -c "from main import start; start()"启动进程 - 输入
start |,"Hello world"会持续输出 - 输入
stop |,"Hello world"停止输出,所有进程正常退出
内容的提问来源于stack exchange,提问作者VirxEC

