在Python中进行线程化

时间:2009-06-02 14:03:16

标签: python multithreading

我是线程新手,并且想知道为各种任务(在服务器环境中)产生大量线程是否不好。与更线性的编程相比,线程占用更多的内存/ CPU吗?

6 个答案:

答案 0 :(得分:18)

如果要使用多个线程,则必须考虑多个事项:

  1. 您只能同时运行#processors线程。 (明显)
  2. 在Python中,每个线程都是一个“内核线程”,它通常需要非常少量的资源(默认情况下在linux上有8 MB的堆栈)
  3. Python有一个全局解释器锁,这意味着只能处理一个python指令,与处理器数量无关。但是,如果您的某个线程等待IO,则会释放此锁。
  4. 我从中得出的结论:

    1. 如果您正在使用IO(Turbogears,Twisted)或正在使用正确编码的扩展模块(numpy),请使用线程。
    2. 如果要同时执行python代码,请使用进程(最简单的多进程模块)

答案 1 :(得分:5)

由于您不熟悉线程,因此还有其他值得注意的事项 - 我更倾向于将其视为值的并行范围。

对于任何给定对象的传统线性/顺序编程,您只有一个线程访问和更改一段数据。由于具有词法范围,这通常是安全的。具体而言,一个函数可以对变量的值进行操作,而不会影响全局值。如果您没有词法范围或不良的词法范围,那么在一个函数中更改名为“foo”的变量的值会影响另一个名为“foo”的变量。现在这种问题不太常见,但仍然很常见,可以暗指。

使用线程,您会以更微妙的方式遇到相同的问题。虽然你仍然有词汇范围帮助你 - 因为一个函数内的局部值“X”独立于另一个函数中称为“X”的另一个本地值,数据结构是可变的这一事实是线程中错误的主要来源。 / p>

具体来说,如果函数传递了一个可变值,那么在线程环境中除非你已经注意了,否则该函数不能保证该值不被其他任何东西改变。

此共享状态是线程系统中可能有90-99%的错误的来源,并且可能非常难以调试。因此,如果您要编写一个线程系统,您应该记住共享值的行进距离 - 即并行访问的范围。

为了限制错误,您可以使用一些已知可行的选项:

  1. 使用无共享状态 - 使用线程安全队列传递共享数据
  2. 围绕所有共享数据放置锁定,并确保在整个代码中使用它。这可能比人们想象的要困难得多。问题来自于你“忘记”锁定一个物体 - 这对人们来说非常容易。
  3. 拥有一个对象 - 一个所有者 - 负责共享状态。允许客户端线程向其询问共享状态中值的副本,这些副本带有令牌。当他们想要更新共享状态时,他们会将消息以及他们拥有的令牌传递回单个对象。然后,所有者可以确定是否发生了更新冲突。
  4. 1与unix管道最相当。 3在逻辑上等同于版本控制,通常称为软件事务存储器。

    1& 3是Kamaelia支持的并发模式,旨在消除由2级引起的错误。(披露,我运行Kamaelia项目)2不受支持,因为它依赖于“始终把一切都做对了”。

    无论您使用哪种方法来解决问题,请记住这个问题,以及处理问题的方法,并提前规划您打算如何处理问题,这样可以免除以后的麻烦。< / p>

答案 2 :(得分:4)

这取决于平台。

Windows线程在创建时必须提交大约1MB的内存。最好是拥有某种线程池,而不是像疯子一样产生线程,以确保你永远不会分配超过固定数量的线程。此外,当您使用Python时,您需要使用Global Interpreter Lock,这会阻碍依赖大量并发线程的代码。

在Unix上,您可以考虑使用不同的进程而不是线程,或者查看其他异步处理方式(Twisted服务器框架有处理异步网络任务的有趣方法,如果您觉得真的很冒险,可以看看在stackless Python,一个完全不使用内核线程的延续框架。

答案 3 :(得分:2)

如果需要并发,可以考虑使用微线程。关于这个主题有一篇很好的文章here。优点是你不会创建占用资源并导致上下文切换的“真实”线程。当然,缺点是您没有利用多核技术。

这是方法Kamaeliastackless

如果您正在进行I / O,您可以考虑使用异步I / O.这可能是一个真正的编程难题,但它可以防止你让线程争夺CPU时间。不幸的是,我不知道在Python中有任何独立于平台的方法。

答案 4 :(得分:1)

线程确实有一些CPU和内存开销,但除非你产生数百或数千个,它通常不是那么重要。更重要的问题是,如果在并发线程之间共享任何可写数据结构,则线程使正确编程变得更加困难。请参阅文章The Problem with Threads以获得解释为何它们不是并发编程的良好抽象的原因。

答案 5 :(得分:1)

周围的优秀答案!我只是想补充一点,如果您决定使用线程池(如果您认为线程适合您的应用程序通常是可取的),那么建议您重用(并可能适应)现有的通用实现,例如Christopher Arndt's,而不是从头开始自己(这总是一项有益的工作,确保,但在使你正常工作和调试代码所需的时间方面效率较低; - )。