为何Python线程会阻塞主线程?添加print/sleep则无此问题
问题原因拆解
这事儿本质是Python的GIL(全局解释器锁)加上解释器的变量缓存优化搞出来的:
CPU密集循环霸占GIL+变量缓存
子线程里的while self.keep_looping循环全是纯CPU操作(就一个self.a += 1),属于CPU密集型任务,会死死占着GIL不放;而且解释器为了提速,会把self.keep_looping这个布尔值缓存到寄存器里,不会每次循环都从内存重新读取。主线程虽然调用stop_thread()把keep_looping改成了False,但子线程根本看不到这个变化,就一直死循环下去。为啥注释掉的代码能解决问题?
print()、time.sleep():这俩都是IO操作,执行时会主动释放GIL,让线程能切换,子线程这时候才有机会去内存里读最新的keep_looping值,自然就能退出循环。if self.a == 'xxxx':这个判断语句会让解释器暂停一下,或者触发变量重新读取,相当于给线程切换留了个口子,子线程就能感知到keep_looping的变化。
- try/except块的影响
有try/except的时候,解释器的优化会更激进——因为try块里的逻辑太简单,没有任何触发GIL释放的操作,所以它会更坚决地缓存keep_looping的值。把try/except删掉后,代码结构变了,解释器的缓存策略也跟着调整,子线程就能读到最新的变量值了。
解决办法
给你几个可行的方案:
- 用threading.Event代替布尔变量
Event是线程安全的,能保证子线程及时收到停止信号,比自己用布尔变量靠谱多了:
import threading import time class Something: def __init__(self): self.thread = threading.Thread(target=self.thread_loop, daemon=True) self.stop_event = threading.Event() self.a = 0 def start_thread(self) -> None: self.thread.start() def stop_thread(self) -> None: self.stop_event.set() print('stop') def thread_loop(self) -> None: print('thread_loop') while not self.stop_event.is_set(): try: self.a += 1 except Exception as err: print(err) blinky = Something() print('start') blinky.start_thread() print('stop') blinky.stop_thread() print('done')
- 循环里加个空的sleep
time.sleep(0)不会让线程真的休眠,但会主动释放GIL,给线程切换留机会:
def thread_loop(self) -> None: print('thread_loop') while self.keep_looping: try: self.a += 1 time.sleep(0) # 释放GIL,让线程能切换 except Exception as err: print(err)
- 用锁强制变量同步
通过锁来控制keep_looping的读取和修改,确保每次循环都从内存读最新值:
class Something: def __init__(self): self.thread = threading.Thread(target=self.thread_loop, daemon=True) self.keep_looping = True self.lock = threading.Lock() self.a = 0 def stop_thread(self) -> None: with self.lock: self.keep_looping = False print('stop') def thread_loop(self) -> None: print('thread_loop') while True: with self.lock: if not self.keep_looping: break try: self.a += 1 except Exception as err: print(err)
内容的提问来源于stack exchange,提问作者Nat
相关产品推荐
相关产品推荐

