在 Python 中使用线程通常涉及到 threading
模块,这是一个用于创建和管理线程的强大工具。如果你的类需要在创建的每个实例中运行一个线程,你可以在类中定义线程的行为,并在类的初始化方法中启动线程。
1、问题背景
在一个项目中,需要使用一个 GSM900 调制解调器和一个树莓派来进行通信。为了方便使用,创建了一个包含相关功能的库 Serialworker.py。在主 Python 应用程序(sniffer.py)中导入该库并使用 serialworker 类中的 start() 函数时,遇到了一个问题:start() 函数一运行,代码就会阻塞,导致无法继续执行后面的代码。
2、解决方案
经过调查,发现问题的原因在于 start() 函数启动了一个线程,而该线程与主线程争用共享资源,导致主线程无法继续执行。为了解决这个问题,需要在 start() 函数中使用适当的锁或条件变量来同步线程之间的访问。
具体代码如下:
class serialworker():
"""This Class instantiates a serial thread and has functions for facilitating communication
to the serial interface as well as some GSM900 specific functions"""
def __init__(self, port='/dev/ttyAMA0', baud=115200, timeout=5, pollspeed=0.1):
"""Initialises the variables used in this class, baud=, timeout= and pollspeed= can be adjusted
But have initial values of 115200, 5 and 0.1 respectively, in the future i could add stopbits and parity here"""
self.port = port
self.baud = baud
self.timeout = timeout
self.pollspeed = pollspeed
self.run = False
self.ser = serial.Serial
"""Command variable description:
self.command = Command to write in next while cycle
self.commandwait = Bool used to determine if write buffer contains a command for writing in the next while cycle
self.commandstat = Variable used to hold the return status of the command that was submitted
self.commandret = Bool used to determine if a command was written to the buffer, as to allow the output to be read in the next while cycle
self.respwait = Bool used to determine if commandstat is ready to be read
"""
self.command = ""
self.commandwait = False
self.commandstat = ""
self.commandret = False
self.respwait = False
self.lock = threading.Lock()
def start(self):
"""Starts a thread of the _readwriteloop() funtion"""
#TODO add thread checking, only 1 thread per serial interface should be allowed
self.run = True
t1 = threading.Thread(target=self.readwriteloop())
t1.start()
print("started")
def checkgsm900online(self):
"""Checks if the GSM900 board is online"""
with self.lock:
gsmser = serial.Serial(
port=self.port,
baudrate=self.baud,
parity=serial.PARITY_NONE,
stopbits=serial.STOPBITS_ONE,
bytesize=serial.EIGHTBITS,
timeout=self.timeout,)
gsmcheck = ("AT"+"\r\n").encode()
gsmser.write(gsmcheck)
sleep(0.03)
gsmbuffer = gsmser.inWaiting()
if gsmbuffer == 0:
gsmser.flush()
gsmser.close()
return False
else:
gsmser.flush()
gsmser.close()
return True
def check(self):
"""Checks if a thread currently exists"""
return self.run
def stop(self):
"""stops running thread"""
with self.lock:
self.run = False
def inputcommand(self, string, flag):
"""Allows for a single command to be inputted, sets the commandwait to true to tell the loop to check its queue.
Optionally you can set flag=False to not wait for a return stat on this command"""
with self.lock:
self.command = (string+"\r\n").encode()
self.commandwait = True
print("waiting for resp")
if flag:
while not self.respwait:
sleep(0.1)
self.respwait = False
return self.commandstat
def readwriteloop(self):
"""Main function of the class that reads serial data from a buffer
And checks for new commands inputted by the inputcommand() function
which output status it will write to the commandstat buffer"""
#TODO write function for retrieving input command return data not just the status
ser = self.ser(
port=self.port,
baudrate=self.baud,
parity=serial.PARITY_NONE,
stopbits=serial.STOPBITS_ONE,
bytesize=serial.EIGHTBITS,
timeout=self.timeout,)
while self.run:
with self.lock:
ser.inWaiting()
buffer = ser.inWaiting()
if buffer != 0:
decodeline = ser.readline().decode('utf-8', "ignore").rstrip('\n')
if len(decodeline) > 2:
if self.commandret:
if 'ERROR' in decodeline:
self.commandstat = decodeline
self.commandret = False
self.respwait = True
elif 'OK' in decodeline:
self.commandstat = decodeline
self.commandret = False
self.respwait = True
elif 'NO CARRIER' in decodeline:
self.commandstat = decodeline
self.commandret = False
self.respwait = True
if self.commandwait:
ser.write(self.command)
#print(self.command)
self.commandwait = False
self.commandret = True
sleep(self.pollspeed)
在更新后的代码中,在 start() 函数和 checkgsm900online() 函数中添加了锁。这样,在访问共享资源时,线程就会被同步,从而避免了争用问题。现在,start() 函数将不再阻塞主线程,代码可以正常运行。
要注意的是,在 Python 中使用锁时,必须确保在所有可能导致死锁的地方释放锁。在上面的代码中,锁只在 start() 函数和 checkgsm900online() 函数中使用,因此不会出现死锁问题。如果需要在代码的其他部分使用锁,则必须确保在适当的地方释放锁。
通过这种方式,你可以在 Python 中有效地使用面向对象的方法来管理多线程任务,提高程序的并发性能和响应速度。