当前位置:  开发笔记 > 编程语言 > 正文

在Python中进行线程化

如何解决《在Python中进行线程化》经验,为你挑选了5个好方法。

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



1> quark..:

为了增加复杂性:

使用线程模块

优点:

在自己的线程中运行任何函数(实际上是任何可调用的)都非常容易.

共享数据即使不容易(锁也不容易:),至少简单.

缺点:

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

使用多处理模块

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

优点:

所有任务的实际并发性(无全局解释器锁定).

扩展到多个处理器,甚至可以扩展到多台机器.

缺点:

进程比线程慢.

进程之间的数据共享比线程更棘手.

内存不是隐式共享的.您要么必须明确地共享它,要么必须挑选变量并来回发送它们.这更安全,但更难.(如果问题越来越重要,Python开发人员似乎正在朝着这个方向推动人们.)

使用事件模型,例如Twisted

优点:

您可以非常精确地控制优先级,而不是执行什么时候执行.

缺点:

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


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


我认为你的复杂性排序几乎完全是倒退的.多线程编程*真的很难正确地完成(几乎没人做).事件编程是不同的,但它真的*很容易理解正在发生的事情,并编写测试证明它做了它应该做的事情.(我说本周末已经在大规模并发网络库上实现了100%的覆盖率).
嗯.我认为事件编程*暴露了复杂性.它迫使你更直接地处理它.你可以说,无论你如何处理它,复杂性都是并发性的固有,我同意你的看法.但是我已经做了一些相当大的线程和基于事件的程序,我认为我支持我所说的:基于事件的程序在我的控制下更多,但实际编码它更复杂.

2> Alex Martell..:

你已经得到了各种各样的答案,从"假线程"一直到外部框架,但我没见过Queue.Queue- CPython线程的"秘密酱".

扩展:只要你不需要重叠纯Python的CPU重处理(在这种情况下你需要multiprocessing- 但它也有自己的Queue实现,所以你可以提出一些需要注意的一般建议我"M给予;-),Python的内置threading将做...但它会做,如果你使用它更好的深思熟虑,例如,如下所示.

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

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

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

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

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

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

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

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


如果你能得到答案我会为这个答案.这是我在Stack Overflow上发现的最发人深省的一个.
希望我可以加倍投票
这是关于线程的一个强有力的一般性答案:所描述的方法反映了从其他多处理语言中选择的方法,包括scala(LinkedBlockingQueue作为Actor的后备构造)和smalltalk。值得一读。

3> Eli Courtwri..:

这取决于你正在尝试做什么,但我不喜欢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()


@newtover:您仍在描述与我的示例中相同的基本生产者/消费者线程情况,因此在这种情况下,Pythonic解决方案仍然使用同步的Queue对象.让每个线程将其结果放在输出值的队列中,并让主线程在闲暇时从队列中检索它们.可以在http://docs.python.org/library/queue.html上找到Queue类的文档,他们甚至可以在http://docs.python.org/library/queue上完成您所描述的内容.的.html#Queue.Queue.join
@newtover:Python规范不保证dict可以同步,但是CPython实现可以。因此,除非您使用的是Jython或PyPy或IronPython之类的东西,否则您可能可以使用普通的dict,具体取决于您的操作。因此,如果您只是在不同的线程中设置字典键/值,那就可以了。但是,如果要遍历dict或读取/修改/重置dict值,则可能需要进行自己的同步,例如:http://docs.python.org/library/threading.html#using语句中的条件和信号量锁定

4> Sam Hasler..:

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

http://www.kamaelia.org/cat-trans-medium.png Kamaelia - 并发变得有用,有趣

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

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

这是来自Pycon 2009的视频.它首先将Kamaelia与Twisted和Parallel Python进行比较,然后展示Kamaelia.

使用Kamaelia轻松并发 - 第1部分(59:08)
与Kamaelia轻松并发 - 第2部分(18:15)


我猜有些人讨厌猫

5> Michael Spar..:

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

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

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

以上面使用裸线程的生产者消费者为例,这在Kamaelia中就是这样:

Pipeline(Producer(), Consumer() )

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

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

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

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

相关链接:

Europython 09教程

每月发布

邮件列表

例子

示例应用

可重复使用的部件(发电机和螺纹)

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

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

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

推荐阅读
手机用户2402851155
这个屌丝很懒,什么也没留下!
DevBox开发工具箱 | 专业的在线开发工具网站    京公网安备 11010802040832号  |  京ICP备19059560号-6
Copyright © 1998 - 2020 DevBox.CN. All Rights Reserved devBox.cn 开发工具箱 版权所有