概述
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操作很快,并不是很密集,用多线程也能高效的解决问题。
最后
以上就是寂寞盼望为你收集整理的Python多进程和多线程的比较Python 并发编程之多线程和多进程的全部内容,希望文章能够帮你解决Python多进程和多线程的比较Python 并发编程之多线程和多进程所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复