Python线程是如何工作的?

4
我在想Python的线程是并发运行还是并行运行?
例如,如果我有两个任务并在两个线程内运行,它们会同时运行还是被安排以并发运行?
我知道GIL和线程仅使用一个CPU核心。
3个回答

12

这是一个需要很多解释的复杂问题。我将选择使用CPython,因为它是最广泛使用的,并且是我有经验的。

  • Python线程是需要本地Python解释器在运行时将其内容编译成字节码的系统线程。GIL是特定于解释器(在本例中为CPython)的锁定,强制每个线程在解释器上获取锁定,无论它们在哪个核心上运行,都会防止两个线程同时运行。

  • 没有 CPU核心可以同时运行多个线程。您需要多个核心才能合理地讨论并行性。并发性与并行性不同-前者意味着在任一线程完成之前可以交错两个线程之间的操作,但两个线程不必同时开始,而后者则意味着可以同时启动操作。如果这使您感到困惑,则有关区别的更好描述在此处

  • 有一些方法可以在单核CPU中引入并发性 - 即拥有在需要时挂起(自我休眠)和恢复的线程 - 但是在单核中没有任何实现并行性的方法。

因为这些事实,作为结果,情况取决于具体情况。

  • 系统线程本质上被设计成并发的-否则就没有操作系统的意义。它们是否实际以这种方式执行取决于任务:是否有原子锁?(正如我们将看到的那样!)

  • 执行CPU绑定计算的线程-在其中大量代码被执行,并且解释器为每行动态调用-会获取GIL上的锁定,防止其他线程执行相同的操作。所以,在那个情况下,仅有一个线程跨所有核心工作,因为没有其他线程可以获取解释器。

话虽如此,线程并不需要一直持有全局解释器锁(GIL),而是在需要时获取和释放锁。因为GIL可以在代码块的结尾处被释放,然后被另一个线程抢占,再在那个代码块的结尾释放,如此循环,所以两个线程可以交错执行操作,虽然它们不能并行运行,但它们肯定可以并发运行。

另一方面,I/O绑定的线程会花费大量时间等待请求完成。这些线程不会获取GIL - 因为没有要解释的内容,何必要获取呢? - 所以可以同时运行多个I/O等待线程,每个线程一个核心。但是,一旦需要将代码编译成字节码(也许您需要处理请求?),就会重新获取GIL。

Python中的进程可以生存于GIL之外,因为它们是包含线程的资源集合。每个进程都有自己的解释器,因此每个进程内的线程只需与自己的同级进程竞争GIL。这就是为什么基于进程的并行是Python中推荐的方法,尽管它总体上消耗更多的资源。

总结

因此,两个在线程中的任务可以并行运行,前提是它们不需要访问CPython解释器。如果它们正在等待I/O请求或正在使用适合的其他语言(例如C)扩展程序,这些扩展程序不需要Python解释器,并使用外部函数接口的话,这样就可能发生。

所有线程都可以按照交错的原子操作并发运行。这些交错的操作有多“原子”- GIL在代码块后被释放吗?在每行后? - 取决于任务和线程。Python线程不必串行执行 - 一个线程完成,然后另一个线程开始 - 因此在这个意义上存在并发性。


1
在CPython中,线程是真正的操作系统线程,并由操作系统并发地调度运行。然而,正如您所指出的那样,GIL意味着只有一个线程会同时执行指令。

1
让我解释一下这是什么意思。线程在同一个虚拟机内运行,因此在同一台物理机器上运行。进程可以在同一台物理机器上或另一台物理机器上运行。如果您的应用程序围绕线程构建,那么您没有做任何事情来访问多台机器。因此,您可以扩展到单个机器上的尽可能多的核心(随着时间的推移,这将是相当多的),但要真正达到Web规模,您仍需要解决多台机器问题。

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