
本文解析 Python 多线程下空 while 循环(忙等待)为何严重拖慢 psutil.net_connections() 等系统调用,并揭示 GIL 调度机制的影响;重点推荐使用 threading.Event 替代轮询,实现高效、低开销的线程协作。
本文解析 python 多线程下空 while 循环(忙等待)为何严重拖慢 `psutil.net_connections()` 等系统调用,并揭示 gil 调度机制的影响;重点推荐使用 `threading.event` 替代轮询,实现高效、低开销的线程协作。
问题核心并非 psutil 本身性能低下,而是空忙循环(busy-waiting)在 CPython 中触发了 GIL(全局解释器锁)的不公平调度行为。当你的 MyThread1 执行 while True: if exit: break 时,它不主动让出 CPU,持续争夺 GIL 时间片。尽管 Python 3.11 的 GIL 切换间隔默认约 5 毫秒(可通过 sys.setswitchinterval() 调整),但一个纯计算型空循环几乎会“饿死”其他线程——尤其是执行 psutil.net_connections() 这类需频繁调用底层系统接口(如 readlink)、依赖 GIL 释放进行 I/O 等待的阻塞操作的主线程。
你观察到的 KeyboardInterrupt 停留在 os.readlink() 和 _shutdown 中的 lock.acquire(),正是典型表现:主线程因无法及时获得 GIL 而卡在系统调用入口;而子线程又因无休止轮询持续持有/争抢 GIL,形成恶性循环。添加 time.sleep(1) 后恢复正常,正是因为 sleep() 是明确的让出点——它主动释放 GIL 并挂起当前线程,使调度器得以将 CPU 时间分配给主线程完成 net_connections()。
✅ 正确做法:用事件驱动替代轮询threading.Event 是专为线程间信号传递设计的轻量同步原语,零 CPU 开销、语义清晰:
import psutil
import threading
import os
pid = os.getpid()
proc = psutil.Process(pid)
# 创建事件对象,初始为未设置状态
wait_event = threading.Event()
def worker_thread(event: threading.Event):
print("Worker thread waiting for signal...")
event.wait() # 阻塞,直到 event.set() 被调用
print("Worker thread resumed, doing work...")
# 启动守护线程(避免主程序退出时卡住)
t1 = threading.Thread(target=worker_thread, args=[wait_event], daemon=True)
t1.start()
# 主线程执行耗时操作 —— 此时完全不受干扰
print("Fetching network connections...")
connections = psutil.net_connections() # 快速返回,不再卡顿
print(f"Found {len(connections)} connections")
# 通知子线程继续执行(可选)
wait_event.set()
# 若需等待子线程结束,再调用 join(本例中 daemon=True,通常无需)
# t1.join()⚠️ 关键注意事项:
立即学习“Python免费学习笔记(深入)”;
-
永远避免空
while循环:它是反模式,浪费 CPU、破坏调度公平性,且在多核环境下无法提升性能(GIL 限制)。 -
优先使用
daemon=True+Event:对于后台辅助任务(如监控、心跳),设为守护线程可避免主程序退出时强制join;Event提供安全、原子的唤醒机制。 -
psutil调用本身是线程安全的,但其底层依赖大量系统调用和文件读取(如/proc/net/),这些操作需要 GIL 协调——因此主线程必须能及时获取 GIL。 - 替代方案还包括
queue.Queue(适合数据传递)、threading.Condition(复杂条件等待),但简单信号场景Event最简洁。
总结:Python 线程协作的本质是“合作式让权”,而非“竞争式抢占”。理解 GIL 的存在与调度逻辑,选择 threading.Event 等内置同步工具,才能写出既高效又健壮的并发代码。


















