我一直试图围绕线程如何在Python中运行,并且很难找到有关它们如何运行的良好信息。我可能只是错过了某个链接或其他东西,但似乎官方文档对这个主题并不是很透彻,而且我找不到一个好的文章。
据我所知,一次只能运行一个线程,活动线程每隔10条指令切换一次?
哪里有一个很好的解释,或者你可以提供一个?了解在使用Python的线程时遇到的常见问题也是非常好的。
答案 0 :(得分:49)
是的,由于全局解释器锁(GIL),一次只能运行一个线程。以下是一些关于此的一些见解的链接:
从最后一个链接开始有趣的引用:
让我解释一切意味着什么。 线程在同一个虚拟内部运行 机器,因此运行相同 物理机器。进程可以运行 在同一台物理机器上或在 另一台物理机器。如果你 建立你的应用程序 线程,你没有做任何事情来访问 多台机器。所以,你可以扩展 单个上有多个核心 机器(这将是很多 随着时间的推移),但要真正达到网络 秤,你需要解决 反正多机器问题。
如果您想使用多核,pyprocessing定义了基于流程的API来进行真正的并行化。 PEP还包括一些有趣的基准。
答案 1 :(得分:35)
Python是一种相当容易使用的语言,但有一些警告。您需要了解的最重要的事情是Global Interpreter Lock。这只允许一个线程访问解释器。这意味着两件事:1)你很少发现自己在python中使用了锁语句,2)如果你想利用多处理器系统,你必须使用单独的进程。编辑:我还应该指出,如果你想绕过GIL,你可以把一些代码放在C / C ++中。
因此,您需要重新考虑为什么要使用线程。如果您想并行化您的应用程序以利用双核架构,您需要考虑将应用程序分解为多个流程。
如果您想提高响应能力,则应考虑使用线程。还有其他选择,即microthreading。您还应该考虑一些框架:
答案 2 :(得分:19)
以下是基本线程示例。它会产生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
方法...
当然,这只是非常基础。您最终希望了解用于线程同步和消息传递的信号量,互斥锁和锁。
答案 3 :(得分:10)
如果各个worker正在进行I / O绑定操作,请在python中使用线程。如果您尝试扩展计算机上的多个核心,或者为python找到一个好的IPC框架,或者选择其他语言。
答案 4 :(得分:4)
注意:无论我在哪里提到thread
,我都指的是python 中的 主题,直到明确说明。
如果你来自C/C++
背景,那么在python中线程的工作方式会略有不同。在python中,在给定时间只有一个线程可以处于运行状态。这意味着python中的线程无法真正利用多个处理内核的功能,因为根据设计,线程不可能在多个内核上并行运行。 / p>
由于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绑定操作(防爆图像处理)。
在早期版本的python中,线程切换过去是在固定的没有python指令之后发生的。它默认设置为 100
。原来它不是很好由于执行单个指令所花费的时间可以,因此决定何时应该进行切换的策略
非常疯狂地从毫秒到甚至秒。因此,在每个 100
指令后发布GIL,无论执行时间是多少,都是一个糟糕的政策。
在新版本中,不使用指令计数作为切换线程的度量,使用可配置的时间间隔。
默认的切换间隔为5毫秒。您可以使用sys.getswitchinterval()
获取当前的切换间隔。
可以使用sys.setswitchinterval()
如果一个线程正在进行一些IO绑定操作(Ex文件系统访问或
网络IO)
只要线程正在等待IO操作完成,就会释放GIL。
<强> Which thread to switch to next?
强>
解释器没有自己的调度程序。在间隔结束时调度的线程是操作系统的决定。
答案 5 :(得分:3)
GIL的一个简单解决方案是multiprocessing模块。它可以用作替换线程模块的drop,但使用多个Interpreter进程而不是线程。因此,对于简单的事物而言,除了普通线程之外,还有一些开销,但如果需要,它可以为您提供真正并行化的优势。 它还可以轻松扩展到多台物理机器。
如果你需要真正大规模的并行化而不是我想要进一步看,但是如果你只想扩展到一台计算机的所有核心或几个不同的核心而没有实现更全面的框架的所有工作,那么是给你的。
答案 6 :(得分:2)
尝试记住GIL设置为每隔一段时间轮询一次,以便显示多个任务的外观。这个设置可以很好地调整,但我提出的建议是线程正在进行的工作或许多上下文切换会导致问题。
我甚至建议在处理器上建立多个父母,并尝试在同一个核心上保留相同的工作。