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"GETTEMP\r\n")
recv = serial_port.read_until(b"END")
print(recv)
time.sleep(0.2)
def change_sensor_mode():
serial_port.write(b"CHANGEMODE2\r\n")
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"CHANGEMODE2\r\n")
but there is surely a more standard way to achieve this. How to handle multiple threads writing to serial
?
CodePudding user response:
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"GETTEMP\r\n")
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"CHANGEMODE2\r\n")
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.