【发布时间】:2010-09-07 01:50:42
【问题描述】:
我一直试图了解线程在 Python 中是如何工作的,但很难找到关于它们如何操作的好信息。我可能只是遗漏了一个链接或其他东西,但似乎官方文档在这个主题上并不是很详尽,而且我还没有找到好的文章。
据我所知,一次只能运行一个线程,并且活动线程每10条左右切换一次?
哪里有很好的解释,或者你能提供一个吗?了解在 Python 中使用线程时遇到的常见问题也非常好。
【问题讨论】:
标签: python multithreading
我一直试图了解线程在 Python 中是如何工作的,但很难找到关于它们如何操作的好信息。我可能只是遗漏了一个链接或其他东西,但似乎官方文档在这个主题上并不是很详尽,而且我还没有找到好的文章。
据我所知,一次只能运行一个线程,并且活动线程每10条左右切换一次?
哪里有很好的解释,或者你能提供一个吗?了解在 Python 中使用线程时遇到的常见问题也非常好。
【问题讨论】:
标签: python multithreading
注意:无论我在哪里提到 thread,我的意思都是 python 中的线程,除非明确说明。
如果您来自C/C++ 背景,python 中的线程工作方式会略有不同。在 python 中,在给定时间只能有一个线程处于运行状态。这意味着 python 中的线程无法真正利用多个处理核心的强大功能,因为从设计上来说,线程不可能在多个核心上并行运行。
由于python中的内存管理不是线程安全的,每个线程都需要对python解释器中的数据结构进行独占访问。这种独占访问是通过一种称为GIL的机制获得的/strong> (全局解释器锁).
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 中,线程切换通常在固定数量的 python 指令之后发生。默认设置为 100。事实证明,这不是一个很好的策略决定何时进行切换,因为执行一条指令所花费的时间可以
从毫秒到甚至一秒都非常疯狂。因此,在每个 100 指令之后释放 GIL,无论它们执行所需的时间是一个糟糕的策略。
在新版本中,不是使用指令计数作为切换线程的指标,而是使用可配置的时间间隔。
默认切换间隔为 5 毫秒。您可以使用 sys.getswitchinterval() 获取当前切换间隔。
这可以使用sys.setswitchinterval()进行更改
如果线程正在执行一些 IO 绑定操作(例如文件系统访问或
网络 IO)
只要线程等待某个 IO 操作完成,GIL 就会释放。
Which thread to switch to next?
解释器没有自己的调度器。在间隔结束时调度哪个线程是操作系统的决定。 .
【讨论】:
GIL 的一个简单解决方案是multiprocessing 模块。它可以用作线程模块的替代品,但使用多个解释器进程而不是线程。正因为如此,对于简单的事情来说,比普通线程有更多的开销,但如果你需要它,它会给你带来真正并行化的优势。 它还可以轻松扩展到多台物理机器。
如果您需要真正的大规模并行化,我会看的更远,但如果您只想扩展到一台计算机的所有内核或几个不同的内核,而不需要实施更全面的框架所需的所有工作,那么是给你的。
【讨论】:
Python 是一种相当容易使用的语言,但也有一些注意事项。您需要了解的最重要的事情是 Global Interpreter Lock。这只允许一个线程访问解释器。这意味着两件事:1)你很少发现自己在 python 中使用 lock 语句;2)如果你想利用多处理器系统,你必须使用单独的进程。编辑:我还应该指出,如果你也想绕过 GIL,你可以将一些代码放在 C/C++ 中。
因此,您需要重新考虑为什么要使用线程。如果您想并行化您的应用以利用双核架构,您需要考虑将您的应用拆分为多个进程。
如果您想提高响应能力,您应该考虑使用线程。不过还有其他选择,即microthreading。您还应该研究一些框架:
【讨论】:
是的,由于全局解释器锁 (GIL),一次只能运行一个线程。以下是一些链接,其中包含对此的一些见解:
在最后一个链接中引用了一个有趣的引述:
让我解释一下这意味着什么。 线程在同一个虚拟中运行 机器,因此在同一台机器上运行 物理机。进程可以运行 在同一台物理机器上或在 另一台物理机器。如果你 围绕构建您的应用程序 线程,你什么也没做 多台机器。所以,你可以缩放 尽可能多的核心在单 机器(这将是相当多的 随着时间的推移),但要真正接触到网络 秤,你需要解决 还是多机问题。
如果你想使用多核,pyprocessing 定义了一个基于进程的 API 来进行真正的并行化。 PEP 还包括一些有趣的基准测试。
【讨论】:
下面是一个基本的线程示例。它将产生 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 方法...
当然,这只是最基本的。您最终会想要了解用于线程同步和消息传递的信号量、互斥体和锁。
【讨论】:
请记住,GIL 设置为每隔一段时间进行一次轮询,以便显示多个任务的外观。这个设置可以微调,但我建议应该有线程正在做的工作,否则大量上下文切换会导致问题。
我什至会建议处理器上的多个父级,并尝试在同一个内核上保留类似的工作。
【讨论】:
如果单个工作人员正在执行 I/O 绑定操作,请在 python 中使用线程。如果您尝试在一台机器上跨多个内核进行扩展,请为 python 找到一个好的 IPC 框架或选择其他语言。
【讨论】: