Python初探并发编程
前言
在用Python进行日常开发中,难免不会遇到一些重复性的,高耗时的任务,如果只是简单的串行运行开发的程序,那么就无法使用系统的全部性能,并且会浪费很多时间,并发编程就能够很好的解决这一类问题,下面一系列的教程是我在学习Python并发编程时的笔记,写成教程的方式方便大家学习。
1.了解基本概念
想要使用并发编程,首先需要了解一些基本的概念:
假设有任务一、二、三。任务一耗时15小时,投入5小时,等待10小时,任务二耗时10小时,投入2小时,等待8小时,任务三耗时10小时,投入10小时。 ps:这里标题的并发编程是大概念,即包括多线程、多进程
1.串行
一次只能执行一个任务,前一个任务完成了才能执行下一个任务(一个人[核心]干活)
2.并行
即Python多进程:多个任务同时进行,在同一个时间段可以进行多个任务(三个人[核心]干活)
3.并发
即Python多线程:在任务一完成投入任务进入等待时间时候任务二开始执行,任务二进入等待时间时任务三开始执行
(一个人[核心]干活)
在Python中,虽然
多线程
和协程
在严格意义上来说是串行(即上图的并发),但是执行效率要比一般的串行程序高得多。一般的串行程序在程序阻塞的时候,只能干等着,不能去做其他的事情,就如用看在线视频缓存时间,一般视频缓存的时候只能干等着,这样的效率是十分的低的。在学会并发编程后,在程序的阻塞期间,其他的任务开始执行,就如在视频缓存的时候聊会天,看下评论,先干其他的事情,提高效率。
2.单线程VS多线程VS多进程
文字看多了会感到很无聊很空洞,还不如几行代码来的实在,下面的代码样例需要有以下的Python基础:
1. 函数装饰器 2. 多线程基本使用 3.多进程基本使用
不懂暂时也问题不大,这篇本来就是 介绍文章,看完之后能对这些术语有大概的认识,对结论有些印象就可以了。
在进行对比之前,首先定义常用的四种类型的场景
- CPU计算密集型
- 磁盘IO密集型
- 网络IO密集型
- 模拟IO密集型
# Cpu计算密集型,150w次运算
def cpu(count=500000,x=1,y=1):
for i in range(count):
x = x + x
y = y + y
# 磁盘读写密集型,50w次写入
def disk(count=500000):
with open('temp.txt','w+') as file:
for x in range(count):
file.write('Writing...\t')
# 网络io密集型
header = {
'User-Agent':'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.132 Safari/537.36',
}
url = 'https://github.com/ckfanzhe'
def request():
try:
page = requests.get(url,header)
html = page.content # 获取内容
return html
except Exception as e:
return f'error!{e}'
# 模拟io密集型
def simulation(count=2):
time.sleep(count)
对比的指标,我们用时间来考量,进行同一任务,用时最少,效率越高
为了使代码看起来更加整洁,这里还需要定义一个简单的 计时器
装饰器,它是用来记录每个函数的运行时间
# 装饰器,用来得到函数运行时间
def timer(name):
def wrapper(func):
def deco(*args,**kw):
typ_e = kw.setdefault('type',None)
to = time.time()
func(*args,**kw)
tw = time.time()
cost = tw -to # 函数运行时间
print(f"{name}-{typ_e} 用时:{cost} 秒")
return deco
return wrapper
下面是单线程、多线程、多进程的运行函数定义
# 单线程
@timer('单线程')
def single(func, type=''):
for i in range(10):
func()
# 多线程
@timer('多线程')
def multi_thread(func, type=''):
thread_list = []
for i in range(10):
th = Thread(target=func, args=())
thread_list.append(th)
th.start()
e = len(thread_list)
while True:
for thr in thread_list:
if not thr.is_alive():
e -= 1
if e <= 0:
break
# 多进程
@timer('多进程')
def multi_process(func, type=''):
process_list = []
for i in range(10):
pr = Process(target=func, args=())
process_list.append(pr)
pr.start()
e = process_list.__len__()
while True:
for pro in process_list:
if not pro.is_alive():
e -= 1
if e <= 0:
break
运行函数的结果:
if __name__ == '__main__':
print(f'本机cpu核心数为{os.cpu_count()}')
print('----单线程----\n')
single(cpu, type="CPU计算密集型")
single(disk, type="磁盘IO密集型")
single(request, type="网络IO密集型")
single(simulation, type="模拟IO密集型")
print('\n----多线程----\n')
multi_thread(cpu, type="CPU计算密集型")
multi_thread(disk, type="磁盘IO密集型")
multi_thread(request, type="网络IO密集型")
multi_thread(simulation, type="模拟IO密集型")
print('\n----多进程----\n')
multi_process(cpu, type="CPU计算密集型")
multi_process(disk, type="磁盘IO密集型")
multi_process(request, type="网络IO密集型")
multi_process(simulation, type="模拟IO密集型")
'''
本机cpu核心数为8
----单线程----
单线程-CPU计算密集型 用时:71.7639057636261 秒
单线程-磁盘IO密集型 用时:2.519289493560791 秒
单线程-网络IO密集型 用时:12.698022365570068 秒
单线程-模拟IO密集型 用时:20.00567054748535 秒
----多线程----
多线程-CPU计算密集型 用时:111.34905052185059 秒
多线程-磁盘IO密集型 用时:10.482915878295898 秒
多线程-网络IO密集型 用时:1.866039752960205 秒
多线程-模拟IO密集型 用时:2.007629632949829 秒
----多进程----
多进程-CPU计算密集型 用时:16.204625368118286 秒
多进程-磁盘IO密集型 用时:2.43847393989563 秒
多进程-网络IO密集型 用时:2.813472032546997 秒
多进程-模拟IO密集型 用时:3.2443151473999023 秒
'''
汇总:
种类 | CPU计算密集型 | 磁盘IO密集型 | 网络IO密集型 | 模拟IO密集型 |
---|---|---|---|---|
单线程 | 71.76 | 2.52 | 12.70 | 20.00 |
多线程 | 111.35 | 10.48 | 1.87 | 2.00 |
多进程 | 16.20 | 2.44 | 2.81 | 3.24 |
分析:
-
CPU密集型任务:多线程对比单线程,不仅没有优势,而且因为Python的GIL全局锁,它需要不多的进行加锁、释放操作,然而切换线程非常耗时,就如一个人同时干很多事情,而且事情间又要不断的来回切换,所以效率要低下很多。多进程,由于是多个核心共同进行计算,相当于几个人同时做同一件事情,所以效率要高很多,但是也取决于参加的人数(CPU核心数)
-
IO密集型任务:它可以是读写文件的磁盘IO,进行请求的网络IO,与数据库进行交互的数据库IO,这一类都是计算量特别少,一般都是耗时在IO的等待上,所以在进行IO密集型任务时,多线程比单线程体现出了明显的优势,虽然磁盘密集型IO比单线程要慢,但只是在操作同一个文件再进行比较下的结果,有一定局限性
-
模拟IO密集型:使用sleep来模拟IO的等待时间,可以明显的体现出多线程的优势,每个任务需要睡眠2s,20个任务就需要睡眠20s,使用单线程的时候,可以明显感受到延时,而使用多线程时,10个sleep是同时运行的,所以最终的时间也就是2s
结论:
- 多进程虽然比单线程快很多,但是它需要更高的性能支持
- 多线程适合IO密集型场景 例如:爬虫、多文件读写、socket编程
- 多进程适合大量运算的场景 例如:机器学习、深度学习、大数据分析