释放GIL的成本是多少?

9 投票
4 回答
4639 浏览
提问于 2025-04-17 04:47

假设我有一个C语言扩展函数,它的功能和Python解释器完全无关。有没有什么理由让我释放全局解释器锁(GIL)呢?

比如说,除了可读性和避免微优化这些问题(这些问题虽然重要,但和我的问题关系不大),还有没有其他理由不这样写代码呢?

Py_BEGIN_ALLOW_THREADS
    a = 1 + 1;
Py_END_ALLOW_THREADS

显然,这段代码很简单,性能可能不会太重要。但是,是否有性能方面的理由在这里释放GIL呢?或者说,GIL应该只在更耗CPU的代码中释放吗?

4 个回答

0

有没有什么理由不释放全局解释器锁(GIL)?

如果C扩展调用了不支持重入的代码,那么当多个Python线程同时调用这个扩展时,可能会出现问题。因此,在这种扩展中,你可能想要避免释放GIL,以防止这种情况发生(当然,你也可以在Python层或C层创建自己的互斥锁来实现这一点,而不影响其他线程)。

或者说,GIL应该只在更耗CPU的代码中释放吗?

释放GIL的另一个主要原因是,当调用一个会阻塞的C扩展(比如在套接字上进行阻塞读取)时,这样可以让其他线程继续运行。这正是当Python解释器在一个线程中执行阻塞操作时发生的情况。

1

如果你有一个C语言的扩展函数,它的工作和Python解释器没有任何关系,那么释放全局解释器锁(GIL)通常是个不错的选择。唯一的缺点就是你需要等着重新获得这个锁。在Python 3.2中,你至少要等1/20秒。

9

GIL其实就是一个普通的互斥锁。锁定或解锁一个没有竞争的互斥锁成本非常低,几乎和改变一个全局变量的成本差不多。但是,如果你经常锁定和解锁一个有竞争的互斥锁,那这个互斥锁的成本就会变得很高。

所以,这种做法通常不是个好主意:

Py_BEGIN_ALLOW_THREADS
    a = 1 + 1;
Py_END_ALLOW_THREADS

这里发生的事情是,你解锁了一个互斥锁,然后马上又尝试去锁它。如果这是两段大代码之间的一个间隔,那就给了其他线程一个运行的机会。但是如果你在多线程的细节上没有问题,那就直接保持锁定状态吧。

在这种情况下,这样做是个好主意:

very_long_computation_requires_gil();
Py_BEGIN_ALLOW_THREADS;
a = a + i;
Py_END_ALLOW_THREADS;
very_long_computation_also_requires_gil();

没有了解具体情况,很难做出准确的判断,而且即使了解了情况,通常没有测试也很难确定。

撰写回答