Python不支持杀死子线程
昨天为我的
我刚开始的想法是后台线程每次运行查询后sleep一段时间,然后再运行查询。但是我马上遇到了一个问题:当主程序退出时,后台线程仍在运行,主窗口无法退出。
在使用其它的库时,比如POSIX的pthread,可以使用ptread_cancel(tid)在主线程中结束子线程。但是Python的线程库不支持这样做,理由是我们不应该强制地结束一个线程,这样会带来很多隐患,应该让该线程自己结束自己。所以在Python中,推荐的一种方法是在子线程中循环判断一个标志位,在主线程中改变该标志位,子线程读到标志位改变,就结束自己。
import threading
classX(threading.Thread):
def __init__(self):
threading.Thread.__init__(self)
self.flag = 1
def run(self):
while self.flag == 1:
sleep(300)
...
如果直接使用这种方法,那么我前面的设计就会出现问题。因为线程会被sleep阻塞一段时间,那么只有在sleep的间隙,才有可能去读取标志位。这样主线程需要等待当前sleep结束才能使子线程退出,进而整个程序才能退出。这种做法是行不通的,你不可能指望用户点击“关闭窗口”后等待几百秒程序才能退出。
当然,也可以使用系统命令kill来杀死整个进程。但问题是这样做既不graceful,又不能保证代码对不同系统的兼容性。
只好换个思路,从原来后台进程的设计改起。定时执行未必非得使用sleep,也可以像crontab那样判断当前时间能不能整除某个值,但这样做不能保证任务在某个时间间隔内只执行一次,因为除数的精度和任务的执行时间不好把握;或者使用timer,但是timer会带来更多线程,增加了复杂度。
于是最后决定使用
def run(self):
self.last = time.time()
while self.flag == 1:
Now = time.time()
if Now - self.last > 300:
self.last = Now
...
这样就既能保证子线程在 flag改变之后尽快退出,又能保证在指定时间间隔内任务只运行一次。但是网友earthengine兄指出这种方法并不妥,代码中不用sleep就变成了忙循环,这样会造成CPU使用率过高的问题,仅仅在循环中间添加一个sleep(0~1)就能大幅度地降低CPU使用,而且关闭程序时1秒钟以内的延迟对于用户来说一般还是可以接受的。
def run(self):
self.last = time.time()
while self.flag == 1:
sleep(1)
Now = time.time()
if Now - self.last > 300:
self.last = Now
...
再深入思考一下,虽然本文中的后台线程从功能上来看似乎用不着考虑太多同步的问题,但最后的退出过程可视为一个线程同步的过程。因此可以采用线程同步的思想来设计后台线程:在正常工作时,后台线程进行带超时的等待,超时后就执行工作;退出时主线程给后台线程发送一个信号,由于后台线程在超时等待,因此接收信号后就终止退出。这样,在用户结束程序时,就不用等待sleep到时了。
import threading
classX(threading.Thread):
def __init__(self):
threading.Thread.__init__(self)
self.flag = 1
self.cond = threading.Condition()
def run(self):
self.cond.acquire()
self.condition.wait(300)
while self.flag == 1:
...
self.cond.release()
self.cond.acquire()
self.condition.wait(300)
...
x.flag = 0
x.cond.acquire()
x.cond.notify()
x.cond.release()
最后,非常感谢earthengine兄的精彩评论,小弟受益良多。