
空 while 循环会持续抢占 gil,使其他线程(如 psutil 的系统调用)无法及时获得执行机会,导致 net_connections() 等 i/o 密集型操作严重延迟;应使用 threading.event 等同步原语替代忙等待。
空 while 循环会持续抢占 gil,使其他线程(如 psutil 的系统调用)无法及时获得执行机会,导致 net_connections() 等 i/o 密集型操作严重延迟;应使用 threading.event 等同步原语替代忙等待。
在 Python 多线程编程中,一个看似无害的空 while True: 循环(即“忙等待”)实则危害巨大——它不仅浪费 CPU 资源,更会严重干扰主线程及其他线程的正常调度,尤其当涉及底层系统调用(如 psutil.net_connections())时,延迟可达分钟级。根本原因在于 CPython 的全局解释器锁(GIL)调度机制:Python 3.11 默认每约 5 毫秒尝试一次线程切换(可通过 sys.setswitchinterval() 调整),但纯计算型循环几乎不触发让出点,导致忙线程长期独占 GIL,而 psutil.net_connections() 内部需频繁调用 os.readlink() 等阻塞式系统调用,这些调用在 GIL 被占用时被迫排队等待,最终引发超长阻塞。
✅ 正确做法:用 threading.Event 实现协作式等待
threading.Event 是专为线程间信号传递设计的轻量同步原语,它让线程在等待时主动释放 GIL,彻底避免忙等待:
import psutil
import threading
import time
# 创建事件对象,初始为未设置状态
stop_event = threading.Event()
def worker_thread():
print("Worker thread started, waiting for signal...")
stop_event.wait() # 安全挂起:自动释放 GIL,不消耗 CPU
print("Worker thread received signal, exiting.")
# 启动工作线程(推荐设为 daemon=True,避免主程序退出卡死)
t = threading.Thread(target=worker_thread, daemon=True)
t.start()
# 主线程执行耗时 I/O 操作 —— 不再被阻塞!
print("Calling psutil.net_connections()...")
start = time.time()
connections = psutil.net_connections()
print(f"✅ Got {len(connections)} connections in {time.time() - start:.2f}s")
# 打印部分端口示例
for con in connections[:5]:
if hasattr(con.laddr, 'port'):
print(f" Port: {con.laddr.port}")
# 通知工作线程退出
stop_event.set()
t.join(timeout=1) # 加超时更健壮
⚠️ 关键注意事项
-
永远不要写
while flag:这类空循环:它是反模式,等价于“CPU 自旋锁”,违反 Python 线程协作原则; -
daemon=True的重要性:若工作线程无需完成清理即可终止,设为守护线程可防止t.join()无限阻塞主程序退出; -
GIL 不是万能锁:它只保护 Python 对象操作,不阻止系统调用(如
readlink)。但若调用前 GIL 未被释放,后续系统调用就可能因调度失衡而排队; -
替代方案扩展:
- 异步场景 → 用
asyncio.Event - 多进程场景 → 用
multiprocessing.Event - 需要定时唤醒 → 结合
threading.Timer或event.wait(timeout=...)
- 异步场景 → 用
? 总结
Python 线程的高效协同依赖于“主动让出”而非“强行抢占”。threading.Event、queue.Queue、threading.Condition 等同步工具均内置 GIL 释放逻辑,是解决此类问题的标准实践。将空循环替换为事件等待,不仅能消除 psutil 等库的异常延迟,更能显著降低 CPU 占用、提升整体响应性——这既是最佳实践,也是理解 CPython 并发模型的关键一步。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











