朱利奥·佛朗哥怎么说 https://stackoverflow.com/a/18114475对于多线程与多处理来说是正确的一般来说.
However, Python* has an added issue: There's a Global Interpreter Lock that prevents two threads in the same process from running Python code at the same time. This means that if you have 8 cores, and change your code to use 8 threads, it won't be able to use 800% CPU and run 8x faster; it'll use the same 100% CPU and run at the same speed. (In reality, it'll run a little slower, because there's extra overhead from threading, even if you don't have any shared data, but ignore that for now.)
但也有例外。如果您的代码的繁重计算实际上并未发生在 Python 中,而是在某些具有执行正确 GIL 处理的自定义 C 代码的库中(例如 numpy 应用程序),您将从线程中获得预期的性能优势。如果繁重的计算是由您运行并等待的某个子进程完成的,情况也是如此。
更重要的是,在某些情况下这并不重要。例如,网络服务器花费大部分时间从网络读取数据包,而 GUI 应用程序花费大部分时间等待用户事件。在网络服务器或 GUI 应用程序中使用线程的原因之一是允许您执行长时间运行的“后台任务”,而无需停止主线程继续服务网络数据包或 GUI 事件。这对于 Python 线程来说效果很好。 (用技术术语来说,这意味着 Python 线程为您提供并发性,即使它们不为您提供核心并行性。)
但是,如果您用纯 Python 编写 CPU 密集型程序,那么使用更多线程通常没有帮助。
使用单独的进程就没有GIL这样的问题,因为每个进程都有自己单独的GIL。当然,与任何其他语言一样,您仍然需要在线程和进程之间进行权衡 - 在进程之间共享数据比在线程之间共享数据更困难且更昂贵,运行大量进程或创建和销毁进程的成本可能会很高但 GIL 严重影响了进程的平衡,而对于 C 或 Java 来说,情况并非如此。因此,您会发现自己在 Python 中比在 C 或 Java 中更频繁地使用多处理。
与此同时,Python 的“自带电池”哲学带来了一些好消息:编写可以通过一行更改在线程和进程之间来回切换的代码非常容易。
如果您根据独立的“作业”设计代码,除了输入和输出之外,不与其他作业(或主程序)共享任何内容,则可以使用concurrent.futures http://docs.python.org/3/library/concurrent.futures.html围绕线程池编写代码的库,如下所示:
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:
唯一真正需要注意的是,作业的参数和返回值必须是可腌制的(并且不会花费太多时间或内存来腌制)才能跨进程使用。通常这不是问题,但有时却是问题。
但如果您的工作不能独立怎么办?如果您可以根据工作来设计代码传递消息从一个到另一个,仍然很容易。你可能需要使用threading.Thread
or multiprocessing.Process
而不是依赖池。你必须创建queue.Queue
or multiprocessing.Queue
明确对象。 (还有很多其他选项——管道、套接字、带有羊群的文件……但重点是,你必须这样做某物如果执行器的自动魔法不够,则手动进行。)
但是如果您甚至不能依赖消息传递怎么办?如果您需要两项工作来改变相同的结构并查看彼此的变化怎么办?在这种情况下,您将需要进行手动同步(锁、信号量、条件等),并且如果您想使用进程,还需要显式共享内存对象来启动。这是多线程(或多处理)变得困难的时候。如果你能避免它,那就太好了;如果你不能,你将需要阅读比某人可以放入 SO 答案更多的内容。
从评论中,您想了解 Python 中线程和进程之间的区别。真的,如果您阅读朱利奥·佛朗哥的答案和我的答案以及我们所有的链接,那应该涵盖所有内容……但是摘要肯定会很有用,所以这里是:
- 线程默认共享数据;进程则不然。
- As a consequence of (1), sending data between processes generally requires pickling and unpickling it.**
- 作为(1)的另一个结果,在进程之间直接共享数据通常需要将其放入低级格式,如值、数组和
ctypes
types.
- 进程不受 GIL 的约束。
- 在某些平台(主要是 Windows)上,创建和销毁进程的成本要高得多。
- 进程有一些额外的限制,其中一些在不同平台上是不同的。看编程指南 http://docs.python.org/3/library/multiprocessing.html#multiprocessing-programming了解详情。
- The
threading
模块不具备某些功能multiprocessing
模块。 (您可以使用multiprocessing.dummy
要在线程之上获取大部分缺失的 API,或者您可以使用更高级别的模块,例如concurrent.futures
不用担心。)
* It's not actually Python, the language, that has this issue, but CPython, the "standard" implementation of that language. Some other implementations don't have a GIL, like Jython.
** If you're using the fork https://docs.python.org/3/library/multiprocessing.html#contexts-and-start-methods start method for multiprocessing—which you can on most non-Windows platforms—each child process gets any resources the parent had when the child was started, which can be another way to pass data to children.