Two threads reading/writing on the same serial port at the same time

Question:

Imagine we have a thread reading a temperature sensor 10 times per second via USB serial port.
And, when a user presses a button, we can change the sensor mode, also via USB serial.

Problem: the change_sensor_mode() serial write operation can happen in the middle of the polling thread "write/read_until" serial operation, and this causes serial data corruption.

How to handle this general multithreading problem with Python serial?

Example:

import threading, serial, time

serial_port = serial.Serial(port="COM2", timeout=1.0)

def poll_temperature():
    while True:
        serial_port.write(b"GETTEMPrn")
        recv = serial_port.read_until(b"END")
        print(recv)
        time.sleep(0.2)

def change_sensor_mode():
    serial_port.write(b"CHANGEMODE2rn")

t1 = threading.Thread(target=poll_temperature)
t1.start()

time.sleep(4.12342)  # simulate waiting for user input

change_sensor_mode()

I was thinking about adding a manual lock = True / lock = False before/after the serial operation in poll_temperature, and then the main thread should wait:

global lock

def change_sensor_mode():
    elapsed = 0
    t0 = time.time()
    while lock and elapsed < 1.0:
        time.sleep(0.010)
        elapsed += 0.010
    serial_port.write(b"CHANGEMODE2rn")

but there is surely a more standard way to achieve this. How to handle multiple threads writing to serial?

Asked By: Basj

||

Answers:

you should use a threading.lock that is shared by any thread that wants to write to this serial port.

import threading

write_lock = threading.Lock()  # all writers have to lock this
read_lock = threading.Lock()  # all readers have to lock this

def poll_temperature():
    while True:
        with write_lock:
            serial_port.write(b"GETTEMPrn")
        with read_lock:
            recv = serial_port.read_until(b"END")
        print(recv)
        time.sleep(0.2)

def change_sensor_mode():
    with write_lock:
        serial_port.write(b"CHANGEMODE2rn")

The way this works is that no two threads can be executing any code that is guarded by the same lock at the same time, so if one thread tries to execute the second function while another thread is executing the write in the first function, then the second thread must wait until the first thread releases the lock.

Using it in a context manager means that if one of the threads failed inside of that block then the lock is automatically released which avoids a deadlock.

Answered By: Ahmed AEK