Python3中,为什么把checkinterval设置得非常小不会让CPU密集型多线程变慢?
from threading import Thread
def countdown(start, end):
while end > start:
end -= 1
def single_thread(n):
countdown(0, n)
def multi_thread(n):
t1 = Thread(target=countdown, args=(0, n // 2))
t2 = Thread(target=countdown, args=(n // 2, n))
t1.start()
t2.start()
t1.join()
t2.join()
if name == ‘main’:
import timeit
import sys
sys.setswitchinterval(1)
print(timeit.timeit("""import gil;gil.multi_thread(10000000);""", number=1))
# 1.07s
sys.setswitchinterval(0.001)
print(timeit.timeit("""import gil;gil.multi_thread(10000000);""", number=1))
# 1.09s
按照我的理解,将切换间隔设置的更小,导致更多次的线程睡眠 /唤醒操作,然后总的执行时间应该变长,但是这里并没有,谁能告诉我是为什么吗? 测试环境为:四核 Ubuntu python3.5
Python3中,为什么把checkinterval设置得非常小不会让CPU密集型多线程变慢?
第一, 线程切换时间依赖于操作系统。
第二, 这个值只是解释器估计的理想时间。在执行较长的函数内并不会主动中断。因为解释器没有线程调度功能, 需要依赖系统。
我理解你的问题是在问Python的GIL和线程调度。在Python 3.2之前,确实有sys.setcheckinterval()这个函数,它控制着GIL的检查频率。但核心理解是:这个设置主要影响线程切换的"时机",而不是"频率"。
对于纯CPU密集型任务,所有线程都在争抢GIL。把checkinterval设小(比如1),只是让解释器更频繁地检查是否应该切换线程,但切换本身的开销是固定的。关键点在于:无论你多久检查一次,同一时刻仍然只有一个线程能执行Python字节码。
举个例子,两个CPU密集型线程:
import sys
import threading
import time
# 旧版Python 3.2之前的方式
sys.setcheckinterval(1) # 设为很小的值
def cpu_task():
n = 0
for i in range(10_000_000):
n += i**0.5
return n
# 创建两个线程
threads = []
for _ in range(2):
t = threading.Thread(target=cpu_task)
threads.append(t)
start = time.time()
for t in threads:
t.start()
for t in threads:
t.join()
print(f"耗时: {time.time() - start:.2f}秒")
实际上,从Python 3.2开始引入了sys.setswitchinterval(),它直接控制线程切换的时间间隔(默认5ms)。但原理不变:GIL的存在使得多线程在CPU密集型任务中无法真正并行,所以调整切换间隔不会显著改变总执行时间——线程数越多,总时间越接近单线程时间乘以线程数。
简单说:GIL是瓶颈,切换频率只是细枝末节。
我是看了一个讲 Python3 GIL 的 PDF 之后做的这个实验,根据这个 PDF 中的描述,当两个线程 A,B 同时运行的时候,假如 A 正在运行,此时 B 会阻塞直到超时,然后设置 gil_drop_request 为 1,A 检测到之后就会释放 GIL 并且通过信号唤醒 B,因此我理解的是此时操作系统就会让 A 睡眠,然后调度 B .是我理解错了吗?
PDF 地址: http://www.dabeaz.com/python/NewGIL.pdf
线程切换的条件一是当前线程主动挂起,二是当前时间片用完。这两个条件都由系统决定,解释器是在系统切换完之后加锁。setswitchinterval 只是在解释器加锁时的一个参照时间,而不是系统的时间片时间。
这个程序里线程一直在运行,用满系统时间片。导致解释器无从按指定参数去控制。

