Python中的线程

Python中用于编写multithreading应用程序的模块是什么? 我知道这个语言和Stackless Python提供的基本并发机制,但是它们各自的优缺点是什么?

为了增加复杂性:

使用线程模块

优点:

  • 在自己的线程中运行任何函数(事实上可以调用)真的很容易。
  • 共享数据如果不容易(locking从来不容易:),至less很简单。

缺点:

  • 正如Juergen所提到的, Python线程实际上并不能在解释器中同时访问状态(这里有一个大的锁,臭名昭着的Global Interpreter Lock 。)实际上这意味着线程对于I / O绑定任务(networking连接,写入磁盘,等等),但是对于做并发计算根本没有用处。

使用多处理模块

在简单的用例中,除了每个任务在其自己的进程中运行,而不是在其自己的线程中运行外,看起来与使用threading完全相同 (几乎是字面上的:如果你采用Eli的例子 ,用multiprocessingreplacemultiprocessing ThreadThreadProcessQueue (模块),它应该运行得很好。)

优点:

  • 所有任务的实际并行性(无全局解释器locking)。
  • 扩展到多个处理器,甚至可以扩展到多个机器

缺点:

  • 进程比线程慢。
  • 进程之间的数据共享比线程更棘手。
  • 内存不是隐式共享的。 你必须明确地分享它,或者你必须腌制variables并且来回发送它们。 这更安全,但更难。 (如果越来越重要的话,Python开发者似乎正朝这个方向推动人们。)

使用事件模型,如Twisted

优点:

  • 你可以非常好地控制优先级,而不是什么时候执行。

缺点:

  • 即使有一个好的库,asynchronous编程通常比线程编程更困难,无论是在理解应该发生的事情方面,还是在debugging实际发生的事情方面都很困难。

所有情况下,我假设你已经理解了涉及多任务的许多问题,特别是如何在任务之间共享数据的棘手问题。 如果由于某种原因,你不知道何时以及如何使用锁和条件,你必须从这些开始。 多任务代码充满了微妙和棘手的问题,在开始之前最好对概念有一个很好的理解。

你已经得到了各种各样的答案,从“虚假线索”一直到外部框架,但是我没有看到任何人提到Queue.Queue – CPython线程的“秘诀”。

扩展:只要你不需要重叠纯Python的CPU处理(在这种情况下,你需要multiprocessing – 但它也有自己的Queue实现,所以你可以用一些需要注意的应用一般我给的build议;-),Python的内置threading将做…但是,如果你使用它,将会做得更好,例如,如下所示。

“忘记”共享内存,据说是线程和多处理的主要优势 – 它不能很好地工作,它不能很好地扩展,永远不会。 使用共享内存仅用于产生子线程之前设置一次的数据结构,之后永远不会更改 – 对于其他任何情况,请使单个线程负责该资源,并通过Queue与该线程通信。

为每个通常想要通过锁保护的资源提供一个专用的线程:一个可变数据结构或其连接组,一个到外部进程(DB,XMLRPC服务器等)的连接,外部文件等。获得一个小的线程池,用于没有或不需要专用资源的通用任务 – 不需要在需要时产生线程,否则线程切换开销将压倒你。

两个线程之间的通信总是通过Queue.Queue – 一种消息传递的forms,多处理的唯一理智的基础(除了事务内存,这是有希望的,但我知道没有生产值得的实现,除了在Haskell)。

pipe理单个资源(或小型资源集合)的每个专用线程侦听特定Queue.Queue实例上的请求。 一个池中的线程等待一个共享的Queue.Queue(队列是稳固的线程安全的, 不会让你失败)。

只需要在某个队列(共享或专用)上排队请求的线程就不需要等待结果,而是继续前进。 线程最终需要一个结果或确认请求队列一对(请求,接收队列)与他们刚刚做的Queue.Queue的一个实例,最后,当响应或确认是必不可less的,以便继续,他们得到(等待)从他们接收队列。 确保你已经准备好了获得错误回应以及真实的回应或者确认(Twisted的deferred s很擅长组织这种结构化的回应,顺便说一下)。

你也可以使用Queue来“驻留”任何一个线程可以使用的资源实例,但不能同时在多个线程之间共享(与某些DBAPI组件的DB连接,与其他的游标等) – 这可以让你放松专用线程需求有利于更多的池化(从共享队列获取的池线程需要可排队资源的请求将从适当的队列获取该资源,如果需要等待等)。

Twisted实际上是一种很好的方式来组织这个minuet(或者根据具体情况可以是方形舞蹈),不仅仅是因为延迟,而是因为它的稳健,高度可扩展的基础架构:你可以安排一些东西只在使用线程或subprocess真正的保证,同时在单个事件驱动的线程中做大部分通常被认为是线程的东西。

但是,我意识到Twisted并不是为了大家 – “专用或池化资源,使用排队,永远不要做任何需要Lock或者Guido禁止,更高级的任何同步程序,比如信号或条件”的方法即使你不能用asynchronous事件驱动的方法包装你的头,仍然会被使用,并且仍然会比我曾经偶然发现的其他广泛使用的线程方法提供更多的可靠性和性能。

这取决于你想要做什么,但是我只偏向于使用标准库中的threading模块,因为它可以很容易地使用任何函数,只需要在一个单独的线程中运行它。

 from threading import Thread def f(): ... def g(arg1, arg2, arg3=None): .... Thread(target=f).start() Thread(target=g, args=[5, 6], kwargs={"arg3": 12}).start() 

等等。 我经常有一个使用Queue模块提供的同步队列的生产者/消费者设置

 from Queue import Queue from threading import Thread q = Queue() def consumer(): while True: print sum(q.get()) def producer(data_source): for line in data_source: q.put( map(int, line.split()) ) Thread(target=producer, args=[SOME_INPUT_FILE_OR_SOMETHING]).start() for i in range(10): Thread(target=consumer).start() 

Kamaelia是一个用于构build具有大量通信进程的应用程序的Python框架。

Kamaelia Kamaelia – 并发变得有用,有趣

在Kamaelia中,您可以从简单的组件构build系统,这些组件可以相互交stream 。 这加速了开发,大量的帮助维护,也意味着你build立自然并发的软件 。 它的目的是任何开发人员,包括新手访问。 这也使它很有趣:)

什么样的系统? networking服务器,客户端,桌面应用程序,基于pygame的游戏,转码系统和pipe道,数字电视系统,垃圾邮件根除者,教学工具,以及相当多的:)

下面是来自Pycon 2009的一段video。首先将Kamaelia与Twisted和Parallel Python进行比较,然后演示Kamaelia。

简单的并发与Kamaelia – 第1部分 (59:08)
与Kamaelia轻松并发 – 第二部分 (18:15)

关于Kamaelia,上面的答案并没有真正涵盖这里的好处。 Kamaelia的方法提供了一个统一的接口,这是一个务实的,不完美的,用于处理单个系统中的线程,生成器和进程以实现并发。

从根本上说,它提供了一个有收件箱和发件箱的运行事物的比喻。 您将邮件发送到发件箱,并且当连线在一起时,邮件从发件箱stream向收件箱。 无论您是使用生成器,线程还是进程,还是与其他系统对话,这个隐喻/ API都是一样的。

“不完美”的部分是由于语法糖没有被添加到收件箱和发件箱(虽然这是讨论中) – 重点是系统的安全/可用性。

以上面的生产者消费者例子为例,在Kamaelia中这成了这个:

 Pipeline(Producer(), Consumer() ) 

在这个例子中,如果它们是线程组件或其他,并不重要,它们之间唯一的区别就是从使用angular度来看是组件的基类。 生成器组件使用列表进行通信,使用Queue.Queues的线程组件和使用os.pipes的进程进行通信。

这种方法背后的原因是为了更难debuggingbug。 在线程化方面 – 或者您拥有的任何共享内存并发性,您面临的头号问题是无意中破坏了共享数据更新。 通过使用消息传递,您可以消除类错误。

如果你在任何地方都使用裸线程和锁,那么你一般都会假设你编写代码时不会犯任何错误。 虽然我们都渴望这一点,但是这是非常罕见的。 通过在一个地方封锁locking行为,可以简化事情可能出错的地方。 (上下文处理程序的帮助,但不帮助上下文处理程序外的意外更新)

很明显,并不是每一段代码都可以写成消息传递和共享的风格,这就是为什么Kamaelia也有一个简单的软件事务存储器(STM),这是一个非常简洁的名字 – 它更像是variables的版本控制 – 也就是检查一些variables,更新它们并提交回来。 如果发生冲突,请冲洗并重复。

相关链接:

  • Europython 09教程
  • 每月发布
  • 邮件列表
  • 例子
  • 示例应用程序
  • 可重复使用的组件(发生器和线程)

无论如何,我希望这是一个有用的答案。 FWIW,Kamaelia的设置背后的核心原因是使并发更安全,更容易在Python系统中使用,没有尾巴摇摆狗。 (即大桶的组件

我可以理解为什么其他卡玛丽亚答案被改变了,因为即使对我来说,它看起来更像一个广告,而不是一个答案。 作为Kamaelia的作者,很高兴看到热情,但我希望这包含更多的相关内容:-)

这就是我的说法,请记住,这个答案在定义上是有偏见的,但是对于我来说,Kamaelia的目标是尝试包装IMO的最佳实践。 我build议尝试几个系统,看看哪个适合你。 (如果这不适合堆栈溢出,对不起 – 我是新来这个论坛:-)

如果我必须使用线程,我会使用Stackless Python的微线程(Tasklets)。

一个完整的在线游戏(massivly multiplayer)围绕着Stackless及其multithreading原则进行构build,因为原本只是为了减轻游戏的大规模多人游戏性能。

CPython中的线程受到广泛的阻碍。 其中一个原因是GIL(一种全局解释器锁),它将执行的许多部分的线程序列化。 我的经验是,以这种方式创build快速应用程序是非常困难的。 我的例子编码,其中所有与线程更慢 – 一个核心(但许多等待input应该有一些性能提升可能)。

使用CPython,如果可能,请使用单独的进程。

如果你真的想把你的手弄脏,你可以尝试使用发生器来伪造协程 。 它可能不是最有效率的工作,但协程确实为您提供合作多任务处理的非常好的控制,而不是在别处find的先发制人的多任务处理。

你会发现一个好处就是,总的来说,使用合作多任务时你不需要锁或者互斥锁,但是对于我来说更重要的优势是“线程”之间几乎为零的切换速度。 当然,Stackless Python也被认为是非常好的。 然后是Erlang,如果它不一定是Python。

合作多任务中最大的缺点可能是普遍缺乏阻止I / O的解决方法。 而在伪造的协程中,你也会遇到这样的问题:你不能在线程内的堆栈顶层之外切换“线程”。

在使用假协程完成了一个稍微复杂的应用程序之后,您将会真正开始欣赏在OS级进行进程调度的工作。