为什么 time.sleep(...) 不受 GIL 影响?

9

据我在研究Python GIL时了解到的,只有一个线程可以被执行(谁持有锁定)。然而,如果这是真的,那么为什么这段代码只需要3秒就能执行完,而不是15秒?

import threading
import time

def worker():
    """thread worker function"""
    time.sleep(3)
    print 'Worker'

for i in range(5):
    t = threading.Thread(target=worker)
    t.start()

按照我的线程感觉,我本以为这需要 3 秒钟,事实也确实如此。但是在了解了 GIL (全局解释器锁)和一次只能执行一个线程后,现在我看着这段代码,就会想,为什么不需要 15 秒钟呢?


1
我认为 sleep() 确实会释放 GIL。此外,Python 强制在每个 N 毫秒或执行 M 条字节码指令后进行线程切换(取决于使用的 Python 版本)。 - martineau
@martineau 如果是这样,"等待" 发生在哪里? - user12388651
我认为线程最终会并行等待。 - martineau
1
一个休眠的线程不会执行。5个同时休眠的线程也不会同时执行。 - user2357112
2个回答

10
Mario的回答是一个很好的高层次回答。如果您对实现细节感兴趣:在CPython中,time.sleep的实现使用Py_BEGIN_ALLOW_THREADS/Py_END_ALLOW_THREADS包装其select系统调用:

https://github.com/python/cpython/blob/7ba1f75f3f02b4b50ac6d7e17d15e467afa36aac/Modules/timemodule.c#L1880-L1882

这些是宏,可以保存和恢复线程状态,同时释放/获取GIL:

https://github.com/python/cpython/blob/7c59d7c9860cdbaf4a9c26c9142aebd3259d046e/Include/ceval.h#L86-L94 https://github.com/python/cpython/blob/4c9ea093cd752a6687864674d34250653653f743/Python/ceval.c#L498


1
非常有趣,将CPython源代码包含在这里,我会好好阅读的,干杯。 - user12388651

5

time.sleep(3)并不等同于一个运行了3秒钟的循环,占用CPU周期并持有GIL锁。该线程会在3秒钟内被切换,从而允许其他线程运行。

请参考此stackoverflow问题,了解为什么time.sleep不是一项CPU密集型操作。总之,该线程将被暂停,直到超时时间已过。

由于所有线程都在休眠,因此CPU上没有进行任何真正的工作,且GIL锁不会阻止这些线程同时工作,因为一开始没有持续的锁定。

GIL锁在多个线程进行CPU密集型工作时才需要考虑。在这种情况下,您会看到您期望的减速。 GIL不会导致纯IO限制的工作减速。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接