我发现在Python 3.4中,有几个不同的多处理/线程库:multiprocessing vs threading vs asyncio。

但我不知道该用哪一个,或者是“推荐的”。它们做的事情是一样的,还是不同的?如果是的话,哪个是用来干什么的?我想在我的计算机上写一个使用多核的程序。但我不知道该学哪个图书馆。


当前回答

博士TL;

做出正确的选择:

我们已经介绍了最流行的并发形式。但问题依然存在——什么时候应该选择哪一个?这实际上取决于用例。根据我的经验(和阅读),我倾向于遵循以下伪代码:

if io_bound:
    if io_very_slow:
        print("Use Asyncio")
    else:
        print("Use Threads")
else:
    print("Multi Processing")

CPU绑定=>多处理 I/O绑定,快速I/O,有限数量的连接=>多线程 I/O受限,慢I/O,多连接=> Asyncio

参考


【注意】:

如果你有一个很长的调用方法(例如,一个包含睡眠时间或惰性I/O的方法),最好的选择是asyncio, Twisted或Tornado方法(协程方法),它与单个线程一起工作作为并发。 asyncio适用于Python3.4及更高版本。 Tornado和Twisted从Python2.7开始就准备好了 Uvloop是超快的asyncio事件循环(Uvloop使asyncio快2-4倍)。


(更新(2019)):

Japranto (GitHub)是一个非常快速的基于uvloop的流水线HTTP服务器。

其他回答

多处理 每个进程都有自己的Python解释器,并且可以在处理器的独立内核上运行。Python multiprocessing是一个包,它支持使用类似于threading模块的API生成进程。多处理包提供了真正的并行性,通过使用子进程而不是线程,有效地避开了全局解释器锁。

当你有CPU密集型任务时,使用多处理。

多线程 Python多线程允许在进程中生成多个线程。这些线程可以共享进程的相同内存和资源。在CPython中,由于全局解释器锁,在任何给定的时间都只能运行一个线程,因此你不能利用多个内核。由于GIL的限制,Python中的多线程并不能提供真正的并行性。

Asyncio Asyncio致力于协作多任务概念。Asyncio任务运行在同一个线程上,因此没有并行性,但它为开发人员提供了更好的控制,而不是操作系统,这是多线程的情况。

关于asyncio相对于线程的优点,在这个链接上有一个很好的讨论。

Python摘要中的多处理VS线程VS AsyncIO

多处理可以并行运行。 多线程和asyncio不能并行运行。

使用英特尔(R)酷睿(TM) i7-8700K CPU @ 3.70GHz和32.0 GB RAM,我用2个进程、2个线程和2个异步任务计算了2到100000之间有多少素数,如下所示。*这是CPU限制计算:

Multiprocessing Multithreading asyncio
23.87 seconds 45.24 seconds 44.77 seconds

因为多处理可以并行运行,所以如上所示,多处理比多线程和asyncio快两倍。

我使用了以下3组代码:

多处理:

# "process_test.py"

from multiprocessing import Process
import time
start_time = time.time()

def test():
    num = 100000
    primes = 0
    for i in range(2, num + 1):
        for j in range(2, i):
            if i % j == 0:
                break
        else:
            primes += 1
    print(primes)

if __name__ == "__main__": # This is needed to run processes on Windows
    process_list = []

    for _ in range(0, 2): # 2 processes
        process = Process(target=test)
        process_list.append(process)

    for process in process_list:
        process.start()

    for process in process_list:
        process.join()

    print(round((time.time() - start_time), 2), "seconds") # 23.87 seconds

结果:

...
9592
9592
23.87 seconds

多线程:

# "thread_test.py"

from threading import Thread
import time
start_time = time.time()

def test():
    num = 100000
    primes = 0
    for i in range(2, num + 1):
        for j in range(2, i):
            if i % j == 0:
                break
        else:
            primes += 1
    print(primes)

thread_list = []

for _ in range(0, 2): # 2 threads
    thread = Thread(target=test)
    thread_list.append(thread)
    
for thread in thread_list:
    thread.start()

for thread in thread_list:
    thread.join()

print(round((time.time() - start_time), 2), "seconds") # 45.24 seconds

结果:

...
9592
9592
45.24 seconds

Asyncio:

# "asyncio_test.py"

import asyncio
import time
start_time = time.time()

async def test():
    num = 100000
    primes = 0
    for i in range(2, num + 1):
        for j in range(2, i):
            if i % j == 0:
                break
        else:
            primes += 1
    print(primes)

async def call_tests():
    tasks = []

    for _ in range(0, 2): # 2 asyncio tasks
        tasks.append(test())

    await asyncio.gather(*tasks)

asyncio.run(call_tests())

print(round((time.time() - start_time), 2), "seconds") # 44.77 seconds

结果:

...
9592
9592
44.77 seconds

They are intended for (slightly) different purposes and/or requirements. CPython (a typical, mainline Python implementation) still has the global interpreter lock so a multi-threaded application (a standard way to implement parallel processing nowadays) is suboptimal. That's why multiprocessing may be preferred over threading. But not every problem may be effectively split into [almost independent] pieces, so there may be a need in heavy interprocess communications. That's why multiprocessing may not be preferred over threading in general.

asyncio(该技术不仅在Python中可用,其他语言和/或框架也有它,例如Boost.ASIO)是一种有效处理来自多个同步源的大量I/O操作的方法,而不需要并行代码执行。因此,它只是针对特定任务的解决方案(确实是一个不错的解决方案!),而不是用于一般的并行处理。

我不是一个专业的Python用户,但作为一个计算机体系结构的学生,我想我可以分享一些我在多处理和多线程之间选择的考虑因素。此外,其他一些答案(甚至在那些投票较高的答案中)都是滥用技术术语,所以我认为也有必要对这些问题进行一些澄清,我将首先进行澄清。

多处理和多线程之间的根本区别在于它们是否共享相同的内存空间。线程共享对相同虚拟内存空间的访问,因此线程交换计算结果(零复制,完全用户空间执行)是高效且容易的。

Processes on the other hand have separate virtual memory spaces. They cannot directly read or write the other process’ memory space, just like a person cannot read or alter the mind of another person without talking to him. (Allowing so would be a violation of memory protection and defeat the purpose of using virtual memory. ) To exchange data between processes, they have to rely on the operating system’s facility (e.g. message passing), and for more than one reasons this is more costly to do than the “shared memory” scheme used by threads. One reason is that invoking the OS’ message passing mechanism requires making a system call which will switch the code execution from user mode to kernel mode, which is time consuming; another reason is likely that OS message passing scheme will have to copy the data bytes from the senders’ memory space to the receivers’ memory space, so non-zero copy cost.

说一个多线程程序只能使用一个CPU是不正确的。很多人这么说的原因是由于CPython实现的一个工件:全局解释器锁(GIL)。由于GIL的存在,CPython进程中的线程是序列化的。因此,多线程python程序似乎只使用了一个CPU。

但是多线程计算机程序通常不局限于一个核心,对于Python来说,不使用GIL的实现确实可以并行运行多个线程,也就是说,同时在多个CPU上运行。(见https://wiki.python.org/moin/GlobalInterpreterLock)。

考虑到CPython是Python的主要实现,可以理解为什么多线程Python程序通常等同于绑定到单核。

With Python with GIL, the only way to unleash the power of multicores is to use multiprocessing (there are exceptions to this as mentioned below). But your problem better be easily partition-able into parallel sub-problems that have minimal intercommunication, otherwise a lot of inter-process communication will have to take place and as explained above, the overhead of using the OS’ message passing mechanism will be costly, sometimes so costly the benefits of parallel processing are totally offset. If the nature of your problem requires intense communication between concurrent routines, multithreading is the natural way to go. Unfortunately with CPython, true, effectively parallel multithreading is not possible due to the GIL. In this case you should realize Python is not the optimal tool for your project and consider using another language.

还有一种替代解决方案,即在用C(或其他语言)编写的外部库中实现并发处理例程,并将该模块导入Python。CPython GIL不会阻塞由该外部库生成的线程。

So, with the burdens of GIL, is multithreading in CPython any good? It still offers benefits though, as other answers have mentioned, if you’re doing IO or network communication. In these cases the relevant computation is not done by your CPU but done by other devices (in the case of IO, the disk controller and DMA (direct memory access) controller will transfer the data with minimal CPU participation; in the case of networking, the NIC (network interface card) and DMA will take care of much of the task without CPU’s participation), so once a thread delegates such task to the NIC or disk controller, the OS can put that thread to a sleeping state and switch to other threads of the same program to do useful work.

在我的理解中,asyncio模块本质上是用于IO操作的多线程的特定情况。

所以: cpu密集型程序,可以很容易地分区到多个进程上运行,但通信有限:如果GIL不存在,则使用多线程(如Jython),如果GIL存在,则使用多进程(如CPython)。

cpu密集型程序,需要在并发例程之间进行密集的通信:如果GIL不存在,则使用多线程,或者使用其他编程语言。

大量的IO: asyncio

博士TL;

做出正确的选择:

我们已经介绍了最流行的并发形式。但问题依然存在——什么时候应该选择哪一个?这实际上取决于用例。根据我的经验(和阅读),我倾向于遵循以下伪代码:

if io_bound:
    if io_very_slow:
        print("Use Asyncio")
    else:
        print("Use Threads")
else:
    print("Multi Processing")

CPU绑定=>多处理 I/O绑定,快速I/O,有限数量的连接=>多线程 I/O受限,慢I/O,多连接=> Asyncio

参考


【注意】:

如果你有一个很长的调用方法(例如,一个包含睡眠时间或惰性I/O的方法),最好的选择是asyncio, Twisted或Tornado方法(协程方法),它与单个线程一起工作作为并发。 asyncio适用于Python3.4及更高版本。 Tornado和Twisted从Python2.7开始就准备好了 Uvloop是超快的asyncio事件循环(Uvloop使asyncio快2-4倍)。


(更新(2019)):

Japranto (GitHub)是一个非常快速的基于uvloop的流水线HTTP服务器。