我一直在试图了解线程在 Python 中是如何工作的,而且很难找到关于它们如何操作的好信息。我可能只是错过了一个链接或其他东西,但似乎官方文档在这个主题上不是很详尽,而且我还没有找到一个好的文章。
据我所知,一次只能运行一个线程,并且活动线程每 10 条左右指令切换一次?
哪里有好的解释,或者你能提供一个吗?了解在 Python 中使用线程时遇到的常见问题也是非常好的。
我一直在试图了解线程在 Python 中是如何工作的,而且很难找到关于它们如何操作的好信息。我可能只是错过了一个链接或其他东西,但似乎官方文档在这个主题上不是很详尽,而且我还没有找到一个好的文章。
据我所知,一次只能运行一个线程,并且活动线程每 10 条左右指令切换一次?
哪里有好的解释,或者你能提供一个吗?了解在 Python 中使用线程时遇到的常见问题也是非常好的。
是的,由于全局解释器锁 (GIL),一次只能运行一个线程。以下是一些关于此的一些见解的链接:
在最后一个链接中引用了一个有趣的引述:
让我解释一下这一切意味着什么。线程在同一虚拟机内运行,因此在同一物理机上运行。进程可以在同一台物理机上运行,也可以在另一台物理机上运行。如果您围绕线程构建您的应用程序,那么您就没有做任何事情来访问多台机器。因此,您可以扩展到单台机器上的尽可能多的核心(随着时间的推移,这将是相当多的),但要真正达到网络规模,您无论如何都需要解决多机问题。
如果你想使用多核,pyprocessing定义了一个基于进程的 API 来进行真正的并行化。PEP还包括一些有趣的基准。
Python 是一种相当容易使用的语言,但有一些警告。您需要了解的最重要的事情是 Global Interpreter Lock。这只允许一个线程访问解释器。这意味着两件事:1) 你很少发现自己在 python 中使用 lock 语句;2) 如果你想利用多处理器系统,你必须使用单独的进程。编辑:我还应该指出,如果你也想绕过 GIL,你可以将一些代码放在 C/C++ 中。
因此,您需要重新考虑为什么要使用线程。如果您想并行化您的应用程序以利用双核架构,您需要考虑将您的应用程序分解为多个进程。
如果你想提高响应能力,你应该考虑使用线程。不过,还有其他选择,即微线程。您还应该研究一些框架:
下面是一个基本的线程示例。它将产生 20 个线程;每个线程都会输出它的线程号。运行它并观察它们的打印顺序。
import threading
class Foo (threading.Thread):
def __init__(self,x):
self.__x = x
threading.Thread.__init__(self)
def run (self):
print str(self.__x)
for x in xrange(20):
Foo(x).start()
正如您所暗示的那样,Python 线程是通过时间片实现的。这就是他们获得“平行”效果的方式。
在我的示例中,我的 Foo 类扩展了线程,然后我实现了该run
方法,这是您希望在线程中运行的代码所在的位置。要启动您start()
在线程对象上调用的线程,它将自动调用该run
方法...
当然,这只是最基本的。您最终会想要了解信号量、互斥体和用于线程同步和消息传递的锁。
注意: 在我提到的任何地方,我都thread
指的是python 中的线程,直到明确说明。
如果您来自C/C++
后台,线程在 python 中的工作方式略有不同。在 python 中,在给定时间只能有一个线程处于运行状态。这意味着 python 中的线程无法真正利用多个处理核心的强大功能,因为按照设计,线程不可能在多个核心上并行运行。
由于python中的内存管理不是线程安全的,每个线程都需要对python解释器中的数据结构进行独占访问。这种独占访问是通过一种称为(全局解释器锁)的机制获得的。GIL
Why does python use GIL?
为了防止多个线程同时访问解释器状态并破坏解释器状态。
这个想法是每当一个线程正在执行时(即使它是主线程),都会获取 GIL,并且在某个预定义的时间间隔之后,当前线程会释放 GIL 并由其他线程(如果有)重新获取。
Why not simply remove GIL?
并不是说它不可能删除 GIL,只是在这样做的过程中,我们最终在解释器中放置了多个锁以序列化访问,这使得即使是单线程应用程序的性能也降低了。
因此,删除 GIL 的成本可以通过降低单线程应用程序的性能来得到回报,而这并不是我们所希望的。
So when does thread switching occurs in python?
线程切换发生在 GIL 释放的时候。那么 GIL 什么时候释放呢?有两种情况需要考虑。
如果线程正在执行 CPU Bound 操作(例如图像处理)。
在旧版本的 python 中,线程切换通常在固定100
数量的 python 指令之后发生。默认设置为可以非常疯狂地从毫秒到甚至一秒。因此,在每条指令之后释放 GIL,100
而不管它们执行的时间是一个糟糕的策略。
在新版本中,不是使用指令计数作为切换线程的指标,而是使用可配置的时间间隔。默认切换间隔为 5 毫秒。您可以使用 获取当前切换间隔sys.getswitchinterval()
。这可以使用更改sys.setswitchinterval()
如果线程正在执行一些 IO 绑定操作(例如文件系统访问或
网络 IO)
每当线程等待某个 IO 操作完成时,GIL 就会被释放。
Which thread to switch to next?
解释器没有自己的调度程序。在间隔结束时调度哪个线程是操作系统的决定。.
如果单个工作人员正在执行 I/O 绑定操作,请在 python 中使用线程。如果你想在一台机器上跨多个内核进行扩展,要么为 python 找到一个好的IPC框架,要么选择一种不同的语言。
GIL 的一种简单解决方案是多处理模块。它可以用作线程模块的替代品,但使用多个解释器进程而不是线程。正因为如此,对于简单的事情来说,它比普通线程的开销要多一些,但如果你需要它,它会给你带来真正并行化的优势。它还可以轻松扩展到多台物理机器。
如果您需要真正的大规模并行化,我会看的更远,但是如果您只想扩展到一台计算机的所有内核或几个不同的内核,而无需进行所有工作来实现更全面的框架,那么这就是为您准备的.
请记住,GIL 设置为每隔一段时间进行一次轮询,以便显示多个任务的外观。这个设置可以微调,但我建议应该有线程正在做的工作,否则大量的上下文切换会导致问题。
我什至会建议多个父母使用处理器,并尝试在同一个内核上保留类似的工作。