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

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

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

此外,我不完全清楚如何实例化一个作业队列,并且只有4个(例如)它们并行运行,而另一个则等待资源释放,然后再执行。

我在文档中find清楚的例子,但不是很详尽; 只要我尝试使事情变得复杂一些,就会收到很多奇怪的错误(比如不能被腌制的方法等等)。

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

你能把我和一些资源联系起来解释这两个模块背后的概念,以及如何正确使用它们来完成复杂的任务吗?

朱利奥·佛朗哥(Giulio Franco)所说的multithreading与多处理一般是一样的

但是,Python *有一个额外的问题:有一个全局解释器锁,它可以防止同一进程中的两个线程同时运行Python代码。 这意味着如果你有8个内核,并且改变你的代码使用8个线程,它将不能使用800%的CPU并且运行速度提高8倍。 它将使用相同的100%CPU并以相同的速度运行。 (实际上,它会运行得慢一点,因为线程有额外的开销,即使你没有任何共享数据,但是现在却忽略了)。

这也有例外。 如果您的代码繁重的计算实际上并不是在Python中发生的,但是在一些使用自定义C代码的库中,像Gnu应用程序一样,您将从线程中获得预期的性能优势。 如果大量计算是由您运行和等待的某个subprocess完成的,则情况也是如此。

更重要的是,这种情况并不重要。 例如,networking服务器花费大部分时间从networking上读取数据包,GUI应用程序大部分时间都在等待用户事件。 在networking服务器或GUI应用程序中使用线程的一个原因是允许您在不停止主线程继续服务networking数据包或GUI事件的情况下执行长时间运行的“后台任务”。 这对Python线程来说工作得很好。 (从技术angular度来说,这意味着Python线程可以提供并发性,尽pipe它们不会提供核心并行性。)

但是如果你用纯Python编写一个CPU绑定的程序,使用更多的线程通常是没有用的。

使用单独的进程没有这样的问题与GIL,因为每个进程有其自己的单独的GIL。 当然,在任何其他语言中,线程和进程之间仍然有相同的折衷 – 在进程之间共享数据比线程之间共享数据更加困难和更昂贵,运行大量进程或创build和销毁他们经常等等。但是,GIL严重地影响了对stream程的平衡,而对于C或者Java这种情况并非如此。 所以,你会发现自己在Python中比使用C或Java更频繁地使用多处理。


同时,Python“包含电池”的理念带来了一些好消息:编写可以在线程和进程之间来回切换的代码非常简单,只需要一个线程就可以完成。

如果你用自包含的“作业”来devise你的代码,除了input和输出之外,其他作业(或者主程序)不能共享任何东西,你可以使用concurrent.futures库在线程池中编写代码喜欢这个:

 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问题,所以你需要切换到进程。 你所要做的就是改变第一行:

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

唯一真正的警告是,你的工作的论点和回报价值必须是可以select的(而不是花费太多的时间和记忆来腌制),以便能够跨越过程。 通常这不是问题,但有时候是这样。


但是如果你的工作不能自给自足呢? 如果你可以根据将消息从一个消息传递到另一个消息的作业来devise你的代码,那么这个过程还是非常简单的。 你可能不得不使用threading.Threadmultiprocessing.Process而不是依靠池。 你将不得不显式创buildqueue.Queuemultiprocessing.Queue对象。 (还有很多其他选项 – pipe道,套接字,文件夹,…但问题是,如果Executor的自动魔法不足,则必须手动执行某些操作。

但是,如果你甚至不能依靠消息传递呢? 如果你需要两个工作来改变相同的结构,并看到彼此的变化呢? 在这种情况下,你将需要手动同步(锁,信号,条件等),如果你想使用进程,明确的共享内存对象来引导。 这是multithreading(或多处理)变得困难的时候。 如果你能避免,那很好; 如果你不能,你将需要阅读的东西比可以放在一个SO的答案。


从一个评论中,你想知道Python中的线程和进程有什么不同。 真的,如果你阅读朱利奥·佛朗哥的答案和我的所有链接,这应该涵盖一切…但总结肯定是有用的,所以在这里:

  1. 线程默认共享数据; stream程不。
  2. 作为(1)的结果,在进程之间发送数据通常需要酸洗和取消它。 **
  3. 作为(1)的另一个结果,在进程之间直接共享数据通常需要将其放入像Value,Array和ctypestypes的低级格式。
  4. 进程不受GIL限制。
  5. 在某些平台(主要是Windows)上,创build和销毁过程要昂贵得多。
  6. 对进程有一些额外的限制,其中一些在不同平台上有所不同。 详细信息请参阅编程指南 。
  7. threading模块没有multiprocessing模块的某些function。 (您可以使用multiprocessing.dummy来获取线程顶部的大部分缺失的API,也可以使用诸如concurrent.futures类的高级模块,而不用担心。

*实际上Python不是这个语言,而是CPython,它是该语言的“标准”实现。 其他一些实现没有GIL,比如Jython。

**如果您正在使用fork启动方法进行多处理(您可以在大多数非Windows平台上使用这种方法),则每个subprocess都会获取父进程启动时的任何资源,这可以是将数据传递给subprocess的另一种方法。

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

如果你想并行化一个计算,你可能会需要multithreading,因为你可能希望线程在同一个内存上合作。

说到性能,与线程相比,线程创build和pipe理的速度更快(因为OS不需要分配全新的虚拟内存区域),线程间通信通常比进程间通信更快。 但是线程更难编程。 线程可以相互干扰,并可以写入对方的内存,但这种情况发生的方式并不总是很明显(由于几个因素,主要是指令重新sorting和内存caching),所以你将需要同步原语来控制访问到你的variables。

我相信这个链接以优雅的方式回答你的问题。

简而言之,如果其中一个子问题需要等待另一个子问题完成,那么multithreading就是好的(例如,在I / O繁重的操作中)。 相反,如果您的子问题可能同时发生,则build议进行多处理。 但是,您不会创build比核心数量更多的进程。

下面是python 2.6.x的一些性能数据,这些数据调用了对IO-约束场景中的多处理性能更高性能的线索的质疑。 这些结果来自40个处理器的IBM System x3650 M4 BD。

IO绑定处理:进程池执行比线程池更好

 >>> 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绑定处理:进程池比线程池执行得更好

 >>> 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 

这些并不是严格的testing,但是他们告诉我,与线程相比,多处理并不是完全不成熟的。

用于上述testing的交互式Python控制台中使用的代码

 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') 

那么,大部分的问题都由Giulio Franco解答。 我将进一步详细说明消费者生产者问题,我想这将使您的解决scheme使用multithreading应用程序的正确轨道。

 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) 

你可以阅读更多关于同步原​​语:

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

伪代码在上面。 我想你应该search生产者 – 消费者问题来获得更多的参考。