我正在编写读取和写入串行设备的python代码。该设备基本上是运行Marlin 3D打印机固件的Arduino Mega。
我的python代码发送一系列GCode命令(由换行符终止的ASCII字符串,包括校验和和行号)。 Marlin以“ok \ n”响应每个成功接收的线路。 Marlin只有一个有限的行缓冲区大小,因此如果它已满,Marlin将暂停发送“ok \ n”响应,直到空间被释放。
如果校验和失败,则Marlin请求使用“Resend:143 \ n”响应再次发送该行。如果要求当前温度,另一个可能的响应是“ok T:{temp value} \ n”。
我的代码使用三个线程。主线程,读线程和写线程。这是代码的精简版本:
class Printer:
def connect(self):
self.s = serial.Serial(self.port, self.baudrate, timeout=3)
self.ok_received.set()
def _start_print_thread(self):
self.print_thread = Thread(target=self._empty_buffer, name='Print')
self.print_thread.setDaemon(True)
self.print_thread.start()
def _start_read_thread(self):
self.read_thread = Thread(target=self._continous_read, name='Read')
self.read_thread.setDaemon(True)
self.read_thread.start()
def _empty_buffer(self):
while not self.stop_printing:
if self.current_line_idx < len(self.buffer):
while not self.ok_received.is_set() and not self.stop_printing:
logger.debug('waiting on ok_received')
self.ok_received.wait(2)
line = self._next_line()
self.s.write(line)
self.current_line_idx += 1
self.ok_received.clear()
else:
break
def _continous_read(self):
while not self.stop_reading:
if self.s is not None:
line = self.s.readline()
if line == 'ok\n':
self.ok_received.set()
continue # if we got an OK then we need to do nothing else.
if 'Resend:' in line: # example line: "Resend: 143"
self.current_line_idx = int(line.split()[1]) - 1
if line: # if we received _anything_ then set the flag
self.ok_received.set()
else: # if no printer is attached, wait 10ms to check again.
sleep(0.01)
在上面的代码中,self.ok_received
是threading.Event
。这个主要是可以正常工作。每隔几个小时,它就会卡在while not self.ok_received.is_set() and not self.stop_printing:
内的_empty_buffer()
循环中。这会通过锁定机器来杀死打印件。
当卡在循环内部时,我可以通过手动发送任何命令来继续打印。这允许读线程设置ok_recieved
标志。
由于Marlin不响应校验和,我想有可能“ok \ n”出现乱码。如果从Marlin收到任何,则读取线程中的第三个if语句应该通过设置标志来处理此问题。
所以我的问题是:我在某个地方是否有可能的竞争条件?在我在整个地方添加锁或将两个线程组合成一个之前,我真的想了解这是如何失败的。任何建议都将不胜感激。
答案 0 :(得分:1)
看起来读取线程可以在窗口中获取一些数据,其中写入线程已经从is_set
循环中断开,但尚未调用self.ok_received.clear()
。因此,当线程仍在处理前一行时,读取线程最终调用self.ok_received.set()
,然后写入线程在完成处理前一条消息后不知不觉地调用clear()
,并且永远不会知道另一条线应该写。
def _empty_buffer(self):
while not self.stop_printing:
if self.current_line_idx < len(self.buffer):
while not self.ok_received.is_set() and not self.stop_printing:
logger.debug('waiting on ok_received')
self.ok_received.wait(2)
# START OF RACE WINDOW
line = self._next_line()
self.s.write(line)
self.current_line_idx += 1
# END OF RACE WINDOW
self.ok_received.clear()
else:
break
Queue
可能是处理此问题的好方法 - 每次读取线程接收到一行时,您希望在写入线程中写一行。如果您将self.ok_received.set()
替换为self.recv_queue.put("line")
,则写入线程每次从Queue
中提取内容时都只能写一行:
def _empty_buffer(self):
while not self.stop_printing:
if self.current_line_idx < len(self.buffer):
while not self.stop_printing:
logger.debug('waiting on ok_received')
try:
val = self.recv_queue.get(timeout=2)
except Queue.Empty:
pass
else:
break
line = self._next_line()
self.s.write(line)
self.current_line_idx += 1
else:
break
您还可以将窗口缩小到您可能赢得的点,并在退出内部self.ok_received.clear()
循环后立即将调用移至while
,但在技术上会有仍然是一场比赛。