关于python:线程和多处理模块之间有什么区别?

What are the differences between the threading and multiprocessing modules?

我正在学习如何使用Python中的threadingmultiprocessing模块并行运行某些操作,并加快代码的速度。

我发现很难理解(可能因为我没有任何理论背景)threading.Thread()对象和multiprocessing.Process()对象之间的区别。

另外,对于我来说,还不完全清楚如何实例化一个作业队列,并且其中只有4个(例如)并行运行,而另一个则在执行之前等待资源释放。

我发现文档中的示例很清楚,但不是非常详尽;一旦我试图把事情复杂化一点,就会收到很多奇怪的错误(比如一个无法处理的方法,等等)。

那么,什么时候应该使用threadingmultiprocessing模块?

你能给我链接一些资源来解释这两个模块背后的概念,以及如何正确地将它们用于复杂的任务吗?


Giulio Franco所说的对于多线程和一般的多处理来说是正确的。好的。型

但是,python*还有一个额外的问题:存在一个全局解释器锁,它可以防止同一进程中的两个线程同时运行python代码。这意味着,如果您有8个内核,并且将代码更改为使用8个线程,那么它将无法使用800%的CPU并以8倍的速度运行;它将使用相同的100%CPU并以相同的速度运行。(实际上,它的运行速度会慢一点,因为线程会带来额外的开销,即使您没有任何共享数据,但暂时忽略这一点。)好的。型

但也有例外。如果您的代码的繁重计算实际上没有在Python中发生,但是在一些库中,使用定制的C代码来执行适当的gil处理,比如numpy应用程序,那么您将从线程中获得预期的性能优势。如果繁重的计算是由运行并等待的某个子进程完成的,则情况也是如此。好的。型

更重要的是,有些情况下这并不重要。例如,网络服务器将大部分时间用于从网络上读取数据包,而GUI应用程序将大部分时间用于等待用户事件。在网络服务器或GUI应用程序中使用线程的一个原因是允许您在不停止主线程继续服务网络包或GUI事件的情况下执行长时间运行的"后台任务"。这对于Python线程来说很好。(在技术术语中,这意味着Python线程提供并发性,即使它们不提供核心并行性。)好的。型

但是,如果您使用纯Python编写一个CPU绑定的程序,那么使用更多线程通常是没有帮助的。好的。型

使用单独的过程对gil没有这样的问题,因为每个过程都有自己的单独gil。当然,线程和进程之间的权衡与其他语言一样,在进程之间共享数据比在线程之间更困难,也更昂贵,运行大量的进程或频繁地创建和销毁进程可能会代价高昂,等等。但是,GIL在向Pro的平衡方面很重要。以一种不正确的方式,比如说C或Java。所以,你会发现在Python中使用多处理比在C或Java中使用更多。好的。型

同时,python的"包含电池"理念带来了一些好消息:编写代码非常容易,只需一行代码就可以在线程和进程之间来回切换。好的。型

如果您根据独立的"作业"来设计代码,这些"作业"除了输入和输出之外不与其他作业(或主程序)共享任何内容,那么可以使用concurrent.futures库围绕线程池编写代码,如下所示:好的。型

1
2
3
4
with concurrent.futures.ThreadPoolExecutor(max_workers=4) as executor:
    executor.submit(job, argument)
    executor.map(some_function, collection_of_independent_things)
    # ...

您甚至可以获得这些作业的结果,并将其传递给其他作业,按执行顺序或完成顺序等等待;有关详细信息,请阅读Future对象部分。好的。

现在,如果发现你的程序一直在使用100%的CPU,添加更多的线程只会使它变慢,那么你就遇到了gil问题,所以你需要切换到进程。你所要做的就是改变第一行:好的。

1
with concurrent.futures.ProcessPoolExecutor(max_workers=4) as executor:

唯一需要注意的是,您的作业的参数和返回值必须是可pickle的(并且不需要花费太多时间或内存来pickle),才能成为可用的跨进程参数和返回值。通常这不是问题,但有时是。好的。

但如果你的工作不能自给自足呢?如果您可以根据将消息从一个传递到另一个的作业来设计代码,那么这仍然非常简单。您可能需要使用threading.Threadmultiprocessing.Process,而不是依赖池。您必须显式地创建queue.Queuemultiprocessing.Queue对象。(还有很多其他选项,比如管道、套接字、带群的文件……但关键是,如果执行器的自动魔力不足,则必须手动执行某些操作。)好的。

但是如果你甚至不能依靠信息传递呢?如果你需要两个工作来改变相同的结构,并看到彼此的变化,那该怎么办?在这种情况下,您需要进行手动同步(锁、信号量、条件等),如果您想使用进程,还需要显式共享内存对象来引导。这是多线程(或多处理)变得困难的时候。如果你能避免它,太好了;如果你不能,你需要读的东西比别人能写的多。好的。

从一个注释中,您想知道在Python中线程和进程之间有什么不同。真的,如果你读到朱利奥·弗朗哥的答案和我的答案,以及我们所有的链接,这些内容应该包括所有内容……但是总结肯定是有用的,所以下面是:好的。

  • 默认情况下,线程共享数据;进程不共享数据。
  • 由于(1)的原因,在工艺之间发送数据通常需要对其进行酸洗和剥除。**
  • 作为(1)的另一个结果,在进程之间直接共享数据通常需要将其放入低级格式,如值、数组和ctypes类型。
  • 工艺不受GIL的约束。
  • 在某些平台(主要是Windows)上,创建和销毁流程的成本要高得多。
  • 对进程有一些额外的限制,其中一些限制在不同的平台上有所不同。有关详细信息,请参阅编程指南。
  • threading模块没有multiprocessing模块的某些功能。(您可以使用multiprocessing.dummy在线程上获取大部分缺失的API,也可以使用更高级别的模块,如concurrent.futures而不必担心。)
  • >*它不是真正的python,语言,有这个问题,而是cpython,该语言的"标准"实现。其他一些实现没有gil,比如jython。好的。

    >如果您使用fork start方法进行多处理,您可以在大多数非Windows平台上,每个子进程都获取子进程启动时父进程拥有的任何资源,这可以是将数据传递给子进程的另一种方法。好的。好啊。


    一个进程中可以存在多个线程。属于同一进程的线程共享相同的内存区域(可以读取和写入非常相同的变量,并且可以相互干扰)。相反,不同的进程存在于不同的内存区域中,并且每个内存区域都有自己的变量。为了进行通信,进程必须使用其他通道(文件、管道或套接字)。

    如果要并行计算,可能需要多线程处理,因为您可能希望线程在同一内存上进行协作。

    说到性能,线程比进程创建和管理更快(因为操作系统不需要分配一个全新的虚拟内存区域),线程间通信通常比进程间通信更快。但是线程更难编程。线程可以相互干扰,并且可以写入对方的内存,但是这种情况并不总是显而易见的(由于几个因素,主要是指令重新排序和内存缓存),因此需要同步原语来控制对变量的访问。


    我相信这个链接能优雅地回答你的问题。

    简而言之,如果其中一个子问题必须等待另一个子问题完成,那么多线程是好的(例如,在I/O繁重的操作中);相反,如果子问题确实可能同时发生,则建议进行多处理。但是,创建的进程不会超过核心的数量。


    下面是一些针对python 2.6.x的性能数据,它调用这些数据来质疑这样一种观点,即线程在IO绑定场景中的多处理具有更高的性能。这些结果来自40处理器的IBM System x3650 M4 BD。

    IO绑定处理:进程池的性能优于线程池

    1
    2
    3
    4
    5
    >>> do_work(50, 300, 'thread','fileio')
    do_work function took 455.752 ms

    >>> do_work(50, 300, 'process','fileio')
    do_work function took 319.279 ms

    CPU绑定处理:进程池的性能优于线程池

    1
    2
    3
    4
    5
    >>> do_work(50, 2000, 'thread','square')
    do_work function took 338.309 ms

    >>> do_work(50, 2000, 'process','square')
    do_work function took 287.488 ms

    这些不是严格的测试,但它们告诉我,与线程相比,多处理并不是完全没有性能。

    用于上述测试的交互式Python控制台中的代码

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    from multiprocessing import Pool
    from multiprocessing.pool import ThreadPool
    import time
    import sys
    import os
    from glob import glob

    text_for_test = str(range(1,100000))

    def fileio(i):
     try :
      os.remove(glob('./test/test-*'))
     except :
      pass
     f=open('./test/test-'+str(i),'a')
     f.write(text_for_test)
     f.close()
     f=open('./test/test-'+str(i),'r')
     text = f.read()
     f.close()


    def square(i):
     return i*i

    def timing(f):
     def wrap(*args):
      time1 = time.time()
      ret = f(*args)
      time2 = time.time()
      print '%s function took %0.3f ms' % (f.func_name, (time2-time1)*1000.0)
      return ret
     return wrap

    result = None

    @timing
    def do_work(process_count, items, process_type, method) :
     pool = None
     if process_type == 'process' :
      pool = Pool(processes=process_count)
     else :
      pool = ThreadPool(processes=process_count)
     if method == 'square' :
      multiple_results = [pool.apply_async(square,(a,)) for a in range(1,items)]
      result = [res.get()  for res in multiple_results]
     else :
      multiple_results = [pool.apply_async(fileio,(a,)) for a in range(1,items)]
      result = [res.get()  for res in multiple_results]


    do_work(50, 300, 'thread','fileio')
    do_work(50, 300, 'process','fileio')

    do_work(50, 2000, 'thread','square')
    do_work(50, 2000, 'process','square')


    嗯,大部分问题都是由朱利奥·弗朗哥回答的。我将进一步阐述消费者-生产者问题,我认为这将使您对使用多线程应用程序的解决方案走上正确的道路。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    fill_count = Semaphore(0) # items produced
    empty_count = Semaphore(BUFFER_SIZE) # remaining space
    buffer = Buffer()

    def producer(fill_count, empty_count, buffer):
        while True:
            item = produceItem()
            empty_count.down();
            buffer.push(item)
            fill_count.up()

    def consumer(fill_count, empty_count, buffer):
        while True:
            fill_count.down()
            item = buffer.pop()
            empty_count.up()
            consume_item(item)

    您可以从以下位置了解有关同步原语的更多信息:

    1
    2
     http://linux.die.net/man/7/sem_overview
     http://docs.python.org/2/library/threading.html

    上面是伪代码。我想你应该搜索生产者-消费者问题以获得更多的参考资料。