Popen子进程记录多GPS接收器串口数据无输出问题排查
子进程读取GPS串口失败:单独运行正常,主进程启动则无输入
问题描述
我需要在单台电脑上同时记录3台GPS接收器的串口输出以对比性能,于是写了主程序通过Popen启动子进程,每个子进程负责读取指定串口、筛选GGA格式NMEA数据并写入CSV。但测试时发现:
- 单独运行子进程脚本
swap-c_ReadCOM.py,能正常捕获串口输入并生成CSV - 主程序启动子进程时,子进程无法捕获串口输入,仅输出
b' ',正则匹配失败,无CSV生成
主程序代码
import time from datetime import datetime from subprocess import Popen, PIPE save_dir = "output_csvs/" sparkfun_port = "COM7" sparkfun_baud = "38400" trimble_port = "COM6" trimble_baud = "38400" duro_port = "COM5" duro_baud = "115200" if __name__ == "__main__": # 生成文件前缀 file_prefix = datetime.now().strftime("%m-%d-%Y-%H:%M:%S") trial_length = input("How long is this trial (in min)? ") antenna = input("Which GPS antenna is being used? ") file_prefix += "_" + antenna + trial_length + "min" # 生成每个接收器的文件路径 sparkfun_path = save_dir + file_prefix + "_sparkfun.csv" trimble_path = save_dir + file_prefix + "_trimble.csv" duro_path = save_dir + file_prefix + "_duro.csv" # 启动子进程 sparkfun = Popen(['python', './swap-c_ReadCOM.py', sparkfun_port, sparkfun_baud, sparkfun_path], stdin=PIPE, stdout=PIPE, stderr=PIPE) trimble = Popen(['python', './swap-c_ReadCOM.py', trimble_port, trimble_baud, trimble_path], stdin=PIPE, stdout=PIPE, stderr=PIPE) duro = Popen(['python', './swap-c_ReadCOM.py', duro_port, duro_baud, duro_path], stdin=PIPE, stdout=PIPE, stderr=PIPE) # 等待试验结束 time.sleep(int(trial_length)*60+1) print("Trial Complete") quit()
子进程脚本swap-c_ReadCOM.py代码
import sys import serial import re import csv def trim_checksum(decoded_str): idx = decoded_str.find('*') if idx != -1: return decoded_str[:idx] return decoded_str filepath = str(sys.argv[3]) ser = serial.Serial(port=sys.argv[1], baudrate=int(sys.argv[2]), bytesize=serial.EIGHTBITS, parity=serial.PARITY_NONE, stopbits=serial.STOPBITS_ONE) while True: # 主进程结束后会被终止 ser_bytes = ser.readline() decoded_bytes = ser_bytes[0:len(ser_bytes) - 2].decode("utf-8") print(decoded_bytes) isGGA = re.search("\$\w\wGGA", decoded_bytes) if isGGA is not None: decoded_bytes = trim_checksum(decoded_bytes) with open(filepath, "a", newline='') as f: split = decoded_bytes.split(",") writer = csv.writer(f) writer.writerow(split)
解决办法
1. 调整子进程的管道与缓冲设置
你在Popen中设置了stdout=PIPE和stderr=PIPE,但子进程的print输出默认是缓冲的,数据会留在管道中无法实时输出,甚至可能阻塞子进程运行。可以做两种调整:
- 不捕获子进程的输出,让它直接输出到终端:
# 修改Popen参数,去掉stdout和stderr的PIPE设置 sparkfun = Popen(['python', './swap-c_ReadCOM.py', sparkfun_port, sparkfun_baud, sparkfun_path])
- 或者启用无缓冲模式,同时在子进程的
print中强制刷新:
主进程启动时加bufsize=0:
sparkfun = Popen(['python', './swap-c_ReadCOM.py', sparkfun_port, sparkfun_baud, sparkfun_path], stdin=PIPE, stdout=PIPE, stderr=PIPE, bufsize=0)
子进程中修改print语句:
print(decoded_bytes, flush=True)
2. 给串口设置超时时间
子进程中serial.Serial默认没有超时,ser.readline()会一直阻塞等待数据,可能导致子进程假死。添加超时参数:
ser = serial.Serial(port=sys.argv[1], baudrate=int(sys.argv[2]), bytesize=serial.EIGHTBITS, parity=serial.PARITY_NONE, stopbits=serial.STOPBITS_ONE, timeout=1)
3. 正确终止子进程,避免数据丢失
主程序用quit()直接退出会强制终止所有子进程,可能导致子进程还没把缓存中的数据写入文件。应该主动终止子进程:
# 替换原有的quit() sparkfun.terminate() trimble.terminate() duro.terminate() # 等待子进程结束 sparkfun.wait() trimble.wait() duro.wait() print("Trial Complete")
4. 优化文件写入逻辑
子进程中每次写入都打开关闭文件,不仅效率低,还可能因为文件未及时刷新导致数据丢失。可以改为一次性打开文件,持续写入:
import sys import serial import re import csv def trim_checksum(decoded_str): idx = decoded_str.find('*') if idx != -1: return decoded_str[:idx] return decoded_str filepath = str(sys.argv[3]) ser = serial.Serial(port=sys.argv[1], baudrate=int(sys.argv[2]), bytesize=serial.EIGHTBITS, parity=serial.PARITY_NONE, stopbits=serial.STOPBITS_ONE, timeout=1) # 一次性打开文件,保持写入状态 with open(filepath, "a", newline='') as f: writer = csv.writer(f) while True: ser_bytes = ser.readline() if not ser_bytes: continue decoded_bytes = ser_bytes[0:len(ser_bytes) - 2].decode("utf-8", errors='ignore') # 加错误处理避免解码失败 print(decoded_bytes, flush=True) isGGA = re.search(r"\$\w\wGGA", decoded_bytes) if isGGA is not None: decoded_bytes = trim_checksum(decoded_bytes) split = decoded_bytes.split(",") writer.writerow(split) f.flush() # 强制刷新文件缓存
5. 确保子进程脚本路径正确
如果主程序运行的工作目录和脚本所在目录不一致,./swap-c_ReadCOM.py会找不到文件。可以改为绝对路径,比如:
import os script_path = os.path.abspath("swap-c_ReadCOM.py") sparkfun = Popen(['python', script_path, sparkfun_port, sparkfun_baud, sparkfun_path])
总结
优先调整管道缓冲和串口超时设置,这是最可能导致子进程无法读取串口的原因。同时优化文件写入和子进程终止逻辑,确保数据完整写入。
内容的提问来源于stack exchange,提问作者Mattman7306
相关产品推荐
相关产品推荐

