关于多线程:线程如何在Python中工作,以及特定于Python线程的常见陷阱是什么?

关于多线程:线程如何在Python中工作,以及特定于Python线程的常见陷阱是什么?

How do threads work in Python, and what are common Python-threading specific pitfalls?

我一直在努力围绕Python中线程的工作方式,很难找到有关它们如何运行的良好信息。我可能只是缺少链接之类的东西,但似乎官方文档对该主题的了解不是很全面,而且我也找不到很好的文章。

据我所知,一次只能运行一个线程,活动线程每10条指令左右切换一次?

哪里有很好的解释,或者您可以提供一个解释?知道在使用Python线程时遇到的常见问题也将非常高兴。


是的,由于全局解释器锁定(GIL),一次只能运行一个线程。以下是一些有关此方面的见解的链接:

  • http://www.artima.com/weblogs/viewpost.jsp?thread=214235
  • http://smoothspan.wordpress.com/2007/09/14/guido-is-right-to-leave-the-gil-in-python-not-for-multicore-but-for-utility-computing/

在最后一个链接中有一个有趣的报价:

Let me explain what all that means.
Threads run inside the same virtual
machine, and hence run on the same
physical machine. Processes can run
on the same physical machine or in
another physical machine. If you
architect your application around
threads, you’ve done nothing to access
multiple machines. So, you can scale
to as many cores are on the single
machine (which will be quite a few
over time), but to really reach web
scales, you’ll need to solve the
multiple machine problem anyway.

如果要使用多核,则pyprocessing定义基于进程的API进行真正的并行化。 PEP还包括一些有趣的基准。


Python是一种相当容易使用的语言,但是有一些警告。您需要了解的最大信息是全局解释器锁定。这仅允许一个线程访问解释器。这意味着两件事:1)您很少在python中使用过lock语句; 2)如果您想利用多处理器系统,则必须使用单独的进程。编辑:我还应该指出,如果您也想绕开GIL,可以将一些代码放在C / C中。

因此,您需要重新考虑为什么要使用线程。如果要并行化应用程序以利用双核体系结构,则需要考虑将应用程序分解为多个进程。

如果要提高响应速度,则应考虑使用线程。但是,还有其他选择,即微线程。您还应该研究一些框架:

  • 无堆栈Python
  • 小菜
  • Gevent
  • 单片眼镜

下面是一个基本的线程示例。它将产生20个线程;每个线程将输出其线程号。运行它,并观察其打印顺序。

1
2
3
4
5
6
7
8
9
10
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方法...

当然,这只是基础知识。您最终将想要了解有关线程同步和消息传递的信号量,互斥量和锁。


如果单个工作人员正在执行I / O绑定操作,请在python中使用线程。如果要在计算机上跨多个内核扩展,请为python找到一个好的IPC框架,或者选择其他语言。


注意:无论我在何处提及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绑定操作(Ex图像处理)。

在旧版本的python中,线程切换通常是在固定数量的python指令之后发生的,默认情况下设置为100。事实证明,自从执行一条指令所花费的时间可以
从毫秒到秒甚至都非常疯狂。因此,每条100指令之后释放GIL,无论它们执行的时间如何,都是一个糟糕的策略。

在新版本中,不是使用指令计数作为切换线程的度量标准,而是使用了可配置的时间间隔。
默认的切换间隔是5毫秒。您可以使用sys.getswitchinterval()获取当前的切换间隔。
可以使用sys.setswitchinterval()

进行更改

如果线程正在执行某些IO绑定操作(例如文件系统访问权限或
网络IO)

只要线程正在等待一些IO操作完成,就会释放GIL。

Which thread to switch to next?

解释器没有自己的调度程序。在间隔结束时调度哪个线程是操作系统的决定。 。


GIL的一个简单解决方案是多处理模块。它可以用作线程模块的替代品,但使用多个解释器进程而不是线程。因此,与简单线程相比,简单事务的开销要大一些,但是如果需要,它可以为您带来真正的并行化的优势。
它还可以轻松扩展到多个物理计算机。

如果您需要真正的大规模并行化,而我只是想进一步扩展一下,但是如果您只想扩展到一台计算机或几个不同内核的所有内核,而无需花很多时间来实现一个更全面的框架,那么是给你的。


请记住,GIL设置为经常轮询,以显示多个任务的外观。此设置可以微调,但是我建议应该进行一些工作,线程正在执行,或者许多上下文切换将导致问题。

我甚至会建议在处理器上有多个父级,并尝试将作业保持在相同的内核上。


推荐阅读