Python线程CPU亲和性设置下的CPU占用与运行时长异常问题咨询
首先,你的实验现象背后的核心原因是CPython的全局解释器锁(GIL),这是CPython最容易让人困惑的特性之一,我来一步步拆解你的疑问:
1. 为何两种设置下核心占用图表不同?
当你把进程亲和性设为单个核心时,操作系统只能把所有Python线程调度到这一个核心上执行——哪怕线程释放了GIL,也只能在这个核心内切换,所以该核心占用率拉满100%。
而当亲和性设为所有处理器时,操作系统的线程调度器可以把不同的Python线程分配到不同的核心上。关键在于你的calc函数里调用的math.sqrt()是C语言实现的标准库函数:CPython在调用这类扩展函数时会主动释放GIL,让其他线程有机会获取GIL并执行代码。这就意味着多个线程可以同时在不同核心上执行sqrt计算,所以你会看到多个核心出现负载。
2. 线程是否会在多个核心间分配?
是的,操作系统完全可以把Python线程调度到不同核心上运行,但GIL限制了同一时刻只有一个线程能执行Python字节码。不过对于像math.sqrt这种释放GIL的C扩展操作,多个线程可以同时在不同核心上执行底层的C代码,这就是你看到多核心负载的原因。
如果你的calc函数是纯Python的计算(比如用Python循环做算术),那即使调度到多核心,同一时刻也只有一个线程在跑,其他线程会等待GIL,这时候多核心的负载就不会明显。
3. 为何运行时长没有变化?
你的任务总计算量是固定的:os.cpu_count()个线程,每个线程执行400万次sqrt计算。
- 当亲和性为单核心时,线程在这个核心上切换执行,虽然GIL会在
sqrt调用时释放,但所有计算都挤在一个核心里完成; - 当亲和性为多核心时,多个线程可以同时在不同核心执行
sqrt,但sqrt本身是非常轻量的计算,并行带来的收益可能被线程调度的开销抵消了,或者总计算量的“总量”没有变化——你相当于把相同的计算量分散到了多个核心,但总耗时并没有显著减少。
简单说,你的计算任务粒度太细,并行的优势没体现出来,如果换成更耗时的C扩展计算,多核心的时长应该会明显缩短。
4. 是不是核心X上的线程需要等待核心Y上的线程完成?
不是的。当线程执行math.sqrt这类释放GIL的操作时,它不需要等待其他线程——每个线程在执行C代码时都是独立的,GIL的释放让其他线程可以同时运行。只有当线程回到Python字节码执行时,才会重新竞争GIL。
如果是纯Python的计算密集型任务,同一时刻只有一个线程能执行,其他线程会处于等待GIL的状态,但你的代码里大部分时间都在执行释放GIL的C函数,所以不存在这种等待。
5. 这是Python特有的现象吗?
是的,这是CPython解释器特有的GIL限制。像Jython、IronPython这类Python实现就没有GIL,它们的多线程可以真正并行执行计算密集型任务,充分利用多核心。而如果是用C/C++写的多线程程序,也没有GIL的限制,计算密集型任务可以直接把负载分散到多个核心,大幅缩短耗时。
最后补充一点:如果想在Python中真正利用多核心处理计算密集型任务,通常的做法是用multiprocessing模块创建多进程——每个进程有自己的Python解释器和GIL,互不干扰,能真正并行执行。
内容的提问来源于stack exchange,提问作者Mohamed Yasser

