在Python中进行线程化

时间:2009-07-27 19:40:50

标签: python multithreading python-stackless

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

7 个答案:

答案 0 :(得分:113)

为了增加复杂性:

使用threading module

优点:

  • 在其中运行任何函数(实际上任何可调用的函数)都非常简单 自己的线程。
  • 分享数据即使不容易(锁也不容易:),at 最简单的。

缺点:

  • 如上所述by Juergen Python线程实际上不能同时访问解释器中的状态(有一个大锁,臭名昭着的Global Interpreter Lock。)这在实践中意味着线程对于I / O绑定很有用任务(联网,写入磁盘等),但对并行计算没有任何用处。

使用multiprocessing模块

在简单的用例中,这看起来与使用threading完全相同,除了每个任务都在自己的进程中运行而不是自己的线程。 (几乎字面意思:如果你采用Eli's example,并将threading替换为multiprocessingThreadProcessQueue(模块)使用multiprocessing.Queue,它应该运行得很好。)

优点:

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

缺点:

  • 进程比线程慢。
  • 进程之间的数据共享比线程更棘手。
  • 内存未被隐式共享。您要么必须明确地共享它,要么必须挑选变量并来回发送它们。这更安全,但更难。 (如果越来越重要的话,Python开发人员似乎正在朝着这个方向推动人们。)

使用事件模型,例如Twisted

优点:

  • 你可以对优先级进行非常精细的控制,而不是在什么时候执行。

缺点:

  • 即使有一个好的库,异步编程通常比线程编程更难,在理解应该发生的事情和调试实际情况方面都很难。

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

答案 1 :(得分:100)

你已经得到了各种各样的答案,从“假线程”一直到外部框架,但我看到没有人提到Queue.Queue - CPython线程的“秘密酱”。 / p>

扩展:只要你不需要重叠纯Python的CPU重处理(在这种情况下你需要multiprocessing - 但它也有自己的Queue实现,所以你可以用一些必要的注意事项来应用我给出的一般建议;-),Python的内置threading会做...但是如果你使用它会更好地做它建议< / em>,例如,如下。

<忘记“忘记”共享内存,据说是线程与多处理的主要优点 - 它不能很好地工作,它不能很好地扩展,从来没有,永远不会。仅在之前设置的数据结构使用共享内存之后产生子线程并且之后从未更改 - 对于其他所有内容,使单个线程负责该资源,并通过Queue与该线程进行通信。

将专用线程专门用于您通常认为要通过锁保护的每个资源:可变数据结构或其内聚组,与外部进程(DB,XMLRPC服务器等)的连接,外部文件,获取一个小的线程池,用于没有或不需要这种专用资源的通用任务 - 在需要时生成线程,或者线程 - 切换开销会让你不知所措。

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

管理单个资源(或小的内聚资源集)的每个专用线程侦听特定Queue.Queue实例上的请求。池中的线程在单个共享Queue.Queue上等待(队列是完全线程安全的,不会在此失败)。

只需要在某个队列(共享或专用)上排队请求的线程就可以在不等待结果的情况下进行排队,然后继续前进。最终需要对请求进行结果或确认的线程将一对(请求,接收队列)与他们刚刚创建的Queue.Queue实例排队,最终,当响应或确认对于继续进行时,它们必不可少(等待)从他们的接收。确保您已准备好获得错误响应以及真实的回复或确认(Twisted的deferred非常善于组织这种结构化响应,BTW!)。

您还可以使用Queue来“停放”任何一个线程可以使用但永远不会在多个线程之间共享的资源实例(与某些DBAPI组件的DB连接,与其他线程的游标等) - 这让您放松专用线程需求,支持更多池化(从共享队列获取的池线程需要可队列资源的请求将从适当的队列中获取该资源,必要时等待等等)。

扭曲实际上是组织这个小步舞曲(或视情况而定)的好方法,不仅仅是因为延迟,而是因为它的声音,坚固,高度可扩展的基础架构:你可以安排使用线程或只有在真正有保证的情况下才会进行子进程,而在单个事件驱动的线程中执行大多数通常被认为是线程的东西。

但是,我意识到Twisted并不适合所有人 - “专用或池资源,使用队列上的wazoo,从不做任何需要锁定的事情,或者Guido禁止,任何更高级的同步程序,如信号量或条件即使您无法绕过异步事件驱动的方法,仍然可以使用“方法”,并且仍然可以提供比我曾经偶然发现的任何其他广泛适用的线程方法更高的可靠性和性能。

答案 2 :(得分:19)

这取决于你想要做什么,但我不喜欢在标准库中使用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()

答案 3 :(得分:12)

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

  

     

(来源:kamaelia.org Kamaelia - 并发变得有用,有趣

     

在Kamaelia中,您可以使用相互通信的简单组件构建系统。这加速了开发,大规模地帮助维护,也意味着您构建自然并发的软件。它可以由任何开发人员访问,包括新手。它也很有趣:)

     

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

这是来自Pycon 2009的视频。首先将Kamaelia与TwistedParallel Python进行比较,然后展示Kamaelia。

Easy Concurrency with Kamaelia - Part 1(59:08)
Easy Concurrency with Kamaelia - Part 2(18:15)

答案 4 :(得分:5)

关于Kamaelia,上面的答案并不能真正涵盖这里的好处。 Kamaelia的方法提供了一个统一的界面,这个界面非常实用,用于处理线程,生成器和线程。单个系统中的并发进程。

从根本上说,它提供了一个包含收件箱和发件箱的运行物品的隐喻。您将消息发送到发件箱,当连接在一起时,消息从发件箱流向收件箱。无论您是使用生成器,线程或进程,还是与其他系统通信,这个比喻/ API都保持不变。

“不完美”的部分原因是由于尚未为收件箱和发件箱添加语法糖(虽然正在讨论中) - 重点关注系统的安全性/可用性。

使用上面的裸线程的生产者消费者示例,这在Kamaelia中变成了这个:

Pipeline(Producer(), Consumer() )

在这个例子中,如果这些是线程组件或其他方面并不重要,唯一的区别是它们之间从使用角度来看是组件的基类。 Generator组件使用列表进行通信,使用Queue.Queues进行线程化组件,使用os.pipes进行处理。

这种方法背后的原因是让难以调试错误。在线程 - 或任何共享内存并发性中,您面临的首要问题是意外破坏共享数据更新。通过使用消息传递,您可以消除一个类错误。

如果您在任何地方都使用裸线程和锁定,那么您通常会假设在编写代码时不会犯任何错误。虽然我们都渴望这一点,但这种情况很少发生。通过在一个地方包装锁定行为,您可以简化出错的地方。 (上下文处理程序有帮助,但无助于上下文处理程序之外的意外更新)

显然不是每一段代码都可以写成消息传递和共享样式,这就是为什么Kamaelia还有一个简单的软件事务内存(STM),这是一个非常好的想法,有一个讨厌的名字 - 它更像是版本控制变量 - 即检查一些变量,更新它们并提交回来。如果发生冲突,请冲洗并重复。

相关链接:

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

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

这就是我的说法,请注意这个答案的定义是有偏见的,但对我而言,Kamaelia的目标是尝试包装IMO的最佳实践。我建议尝试一些系统,看看哪个适合你。 (如果这不适合堆栈溢出,对不起 - 我是这个论坛的新手: - )

答案 5 :(得分:2)

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

整个在线游戏(大型多人游戏)是围绕Stackless及其多线程原则构建的 - 因为原版只是为了减轻游戏的大型多人游戏属性。

CPython中的主题被广泛劝阻。一个原因是GIL - 一个全局解释器锁 - 它为执行的许多部分序列化线程。我的经验是,用这种方式创建快速应用程序真的很困难。我的示例编码所有线程都较慢 - 有一个核心(但很多等待输入应该可以提高性能)。

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

答案 6 :(得分:1)

如果你真的想亲自动手,你可以试试using generators to fake coroutines。它可能不是最有效的工作方式,但是协程确实为您提供了很好的控制合作多任务处理,而不是你可以在其他地方找到的先发制人的多任务处理。

您将发现的一个优点是,在使用合作多任务时,您不需要锁或互斥锁,但对我来说更重要的优势是“线程”之间的切换速度几乎为零。当然,据说Stackless Python也非常好用;然后是Erlang,如果它不 是Python。

合作多任务处理的最大缺点可能是通常缺乏阻止I / O的解决方法。在伪造的协同程序中,你还会遇到一个问题,即除了线程中堆栈的顶层之外,你无法切换“线程”。

在使用伪协程制作一个稍微复杂的应用程序后,您将真正开始欣赏在操作系统级别进行进程调度的工作。