按照设计,Python 将并发性问题留给了操作系统,而只为操作系统机制提供了一个简单的包装器。Python 字节码直接在单核处理器上运行。一次只执行一个线程。通过获取 GIL,执行中的线程会将其他线程锁在外面。这种简单稳健的设计也使其更容易编写扩展。
当部分线程为 I/O 密集型时,多线程性能保持良好,但是当所有线程为 CPU 密集型时,性能会变差,这对于科学计算来说是个大问题,因为大部分任务都是 CPU 密集型任务。摆脱 GIL 看似微不足道,但做起来并不简单。到目前为止,从 CPython 移除 GIL 的尝试还不是很成功。大部分尝试不是会影响单线程操作模式的性能,就是会破坏与扩展的可兼容性,这两种情况都不是理想的结果。
一种替代方法就是生成进程而不是线程。Python 利用多处理模块实现这项功能。每个进程都有自己的 GIL,因此互不影响。在 Python3 中,这项功能包含在新的 concurrent.futures 模块中。这种方法会有一些开销,但是这样 Python 可以使用多核 CPU 和 CPU 集群。
正如我们前面所见,一些编译器可以围绕一段代码显式释放 GIL。因此,扩展可以在将控件转交给外部库时释放 GIL,并在控件返回至 Python 代码时重新获取。这项功能允许 Python 切换到 C 代码,这种代码可以处理多线程或多处理任务。Python 中的科学编程包(比如 NumPy 和 SciPy)就使用这种方法。
扫码关注