Python多进程和多线程的比较

本文介绍了Python中的多线程和多进程,对比了并发和并行的概念。在多线程中,由于资源竞争可能导致数据紊乱,而Asyncio通过任务状态切换避免了这一问题。多进程在CPU密集型任务中表现出优势,而Asyncio适合处理I/O密集型任务。根据任务类型选择合适的并发模型是关键,如I/O密集型任务可选用Asyncio或Threading,CPU密集型任务则推荐使用多进程。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Python 并发编程之多线程和多进程

概念

并发

在python中,并发并不是指同一时刻有多个操作(thread, task)同时进行,相反,在某个特定的时刻,它只允许又一个操作发生,只不过线程/任务之间会相互切换,直到完成。

  • Threading

  • Asyncio

    对于Threading, 操作系统知道每个线程的所有信息,因此它会做主在适当的时候做线程切换。好处是代码容易书写,但是在执行类似与(x+=1)这种操作的时候,可能会出现资源竞争的的情况。导致数据发生紊乱。

    对于Asyncio而言,主程序想要切换任务时,必须得到此任务可以被切换的通知(任务的状态),这样以来就可以避免上面的资源竞争的问题了。

并行

并行,指的才是同一时刻,同时发生,Python中的multi-processing便是这个意思,可以理解为电脑的多核的处理器,每个处理器都会并行的执行计算自己的程序,互不干扰,从而加速了运行速度。


实战

多线程处理CPU bond任务

def cpu_bond(number):
    return sum(i * i for i in range(number))

def calculate_sums(numbers):
    with concurrent.futures.ThreadPoolExecutor() as executor:
        executor.map(cpu_bond, numbers)

def main_multi_threading():
    start_time = time.perf_counter()
    numbers = [10000000 + x for x in range(20)]
    calculate_sums(numbers)
    end_time = time.perf_counter()
    print('multil-threading:{}'.format(end_time-start_time))

执行结果

multil-threading:17.843226651

多进程处理CPU bond 任务

def cpu_bond(number):
    return sum(i*i for i in range(number))

def calc_sums(numbers):
    with concurrent.futures.ProcessPoolExecutor() as exec_pro:
        exec_pro.map(cpu_bond, numbers)

def main_multi_processing():
    start_time = time.perf_counter()
    numbers = [10000000 + x for x in range(20)]
    calc_sums(numbers)
    end_time = time.perf_counter()
    print('multil-processing:{}'.format(end_time - start_time))

执行结果

multil-processing:4.849198713

由上,我们可以得出结论,在CPU密集型(大量计算,多个程序执行…)的任务中,多进程的优势是明显高于多线程的,因为多线程内部在部分线程有阻塞的时候会去切换到其他线程所产生的时间上的开销,导致了多线程是不适合与CPU密集型的任务;


Threading处理 I/O bond任务

import concurrent.futures
import requests
import threading
import time

def download_one(url):
   resp = requests.get(url)
   print('Read {} from {}'.format(len(resp.content), url))

def download_all(sites):
   with concurrent.futures.ThreadPoolExecutor(max_workers=5) as executor:
       executor.map(download_one, sites)

def main():
   sites = [
       'https://en.wikipedia.org/wiki/Portal:...',
       'https://en.wikipedia.org/wiki/Portal:...',
       'https://en.wikipedia.org/wiki/Portal:...',
   ]
   start_time = time.perf_counter()
   download_all(sites)
   end_time = time.perf_counter()
   print('Download {} sites in {} seconds'.format(len(sites), end_time - start_time))

if __name__ == '__main__':
   main()

执行结果

Read 151021 from https://en.wikipedia.org/...
Read 129886 from https://en.wikipedia.org/...
Read 107637 from https://en.wikipedia.org/wiki/Portal:...

Download 15 sites in 0.19936635800002023 seconds

Asyncio 处理 I/O bond 任务

async def download_one(url):
   async with aiohttp.ClientSession() as session:
       async with session.get(url) as resp:
           print('read {} from {}'.format(resp.content_length, url))

async def download_all(sites):
   # asyncio.create_task(coro),表示对输入的协程创建一个任务,安排它的执行,并返回此任务对象。python3.7新增的,之前的版本可以用
   # asyncio.ensure_future(coro)等效代替,
   tasks = [asyncio.create_task(download_one(site)) for site in sites]
   # asyncio.gather(*aws, loop=None, reture_exception=False),则表示enent loop
   # 中运行aws序列的所有任务。
   await asyncio.gather(*tasks)

def main():  
   sites = [ 
   	"https://en.wikipedia.org/wiki/... ",
   	"https://en.wikipedia.org/wiki/... "
   	"https://en.wikipedia.org/wiki/... "
   	"..."
] 
   start_time = time.perf_counter()
   download_all(sites)
   end_time = time.perf_counter()
   print('Download {} sites in {} seconds'.format(len(sites), end_time - start_time))

执行结果

Read 129886 from https://en.wi...
Download 15 sites in 2.4642311 seconds

multi-processing 处理I/O bond 任务

import requests
import concurrent.futures

def down_one(url):
   resp = requests.get(url)
   print('Read {} from {}'.format(len(resp.content), url))

def down_all(sites):
   with concurrent.futures.ProcessPoolExecutor() as pe:
       pe.map(down_one, sites)

def main_processing():
   sites = [
       'https://en.wikipedia.org/wiki/Portal:...',
       'https://en.wikipedia.org/wiki/Portal:...',
       'https://en.wikipedia.org/wiki/Portal:...',
   ],
   start_time = time.perf_counter()
   down_all(sites)
   end_time = time.perf_counter()
   print('Download {} sites in {} seconds'.format(len(sites), end_time - start_time))

if __name__ == '__main__':
   main_processing()

执行结果

Read 129886 from https://en.wi...
Download 15 sites in 16.4642311 seconds

可以发现mutil-processing处理I/O heavy 的任务来说是非常的低效了,所以根据具体情景来选择正确的方法,才是一个好的coder。


总结

通过上面的大量测试,我们遇到实际的问题,该如何选择?

  • 如果是 I/O bond ,并且I/O 操作很慢,需要多任务/线程协同实现,那么使用Asyncio更为合适。
  • 如果是I/O bond,但是I/O操作很快,只需要有限数量的任务/线程,那么Threading即可。
  • 如果是CPU bond,则需要使用multi-processing来提高程序的运行效率。

不同于多线程,Asyncio是单线程的,但是其内部event loop的机制,可以让它并发的运行多个不同的任务,并且多线程享有更大的自主控制权。Asyncio中的任务,在运行的过程中不会被打断,因此不会出现资源竞争的情况。另外Asyncio比多线程的运行效率要高,因为Asyncio内部task切换的损耗,远比线程间切换的损耗要小,并且Asyncio可以开启的任务数量,也比多线程中的线程数量要多的多。
很多情况下,使用Asyncio需要特定的第三方库支持,例如aiohttp,而如果I/O操作很快,并不是很密集,用多线程也能高效的解决问题。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值