串行读/写代码中可能的竞争条件

时间:2014-11-04 17:43:47

标签: python multithreading serial-port python-multithreading

我正在编写读取和写入串行设备的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_receivedthreading.Event。这个主要是可以正常工作。每隔几个小时,它就会卡在while not self.ok_received.is_set() and not self.stop_printing:内的_empty_buffer()循环中。这会通过锁定机器来杀死打印件。

当卡在循环内部时,我可以通过手动发送任何命令来继续打印。这允许读线程设置ok_recieved标志。

由于Marlin不响应校验和,我想有可能“ok \ n”出现乱码。如果从Marlin收到任何,则读取线程中的第三个if语句应该通过设置标志来处理此问题。

所以我的问题是:我在某个地方是否有可能的竞争条件?在我在整个地方添加锁或将两个线程组合成一个之前,我真的想了解这是如何失败的。任何建议都将不胜感激。

1 个答案:

答案 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,但在技术上会有仍然是一场比赛。

相关问题