python中实现并发的手段之 协程
几种实现并发的手段
进程
启动多个进程 进程之间是由操作系统负责调用
线程
启动多个线程 真正被CPU执行的最小单位实际是线程
开启一个线程 创建一个线程 寄存器 堆栈
关闭一个线程
协程
本质上是一个线程
能够在多个任务之间切换来节省一些IO时间
协程中任务之间的切换也消耗时间,但是开销要远远小于进程线程之间的切换
通过生成器来实现的协程
import time def consumer(): while True: x = yield time.sleep(1) print('处理了数据 :',x) def producer():
# 创建一个生成器对象 c = consumer()
# 激活这个生成器 next(c) for i in range(10): time.sleep(1) print('生产了数据 :',i) c.send(i) producer()
通过greenlet模块实现的协程
真正的协程模块就是使用greenlet完成的切换 from greenlet import greenlet def eat(): print('eating start') g2.switch() print('eating end') g2.switch() def play(): print('playing start') g1.switch() print('playing end')
g1 = greenlet(eat) g2 = greenlet(play) g1.switch()
通过genevt模块实现的协程
可以看出来,greenlet只能实现两个代码之间的切换,但是我们使用协程的主要原因是在IO请求时,达到非阻塞的作用,所以我们需要使用gevent模块来让代码可以遇到阻塞就自由的切换
from gevent import monkey;monkey.patch_all() import time import gevent import threading def eat(): print(threading.current_thread().getName()) print(threading.current_thread()) print('eating start') time.sleep(1) print('eating end') def play(): print(threading.current_thread().getName()) print(threading.current_thread()) print('playing start') time.sleep(1) print('playing end') g1 = gevent.spawn(eat) g2 = gevent.spawn(play) g1.join() g2.join()
同步和异步的性能比较
进程和线程的任务切换由操作系统完成 协程任务之间的切换由程序(代码)完成,只有遇到协程模块能识别的IO操作的时候,程序才会进行任务切换,实现并发的效果 同步 和 异步 from gevent import monkey;monkey.patch_all() import time import gevent def task(n): time.sleep(1) print(n) def sync(): for i in range(10): task(i) def async(): g_lst = [] for i in range(10): g = gevent.spawn(task,i) g_lst.append(g) gevent.joinall(g_lst) # for g in g_lst:g.join() sync() async()
什么时候可以使用协程??
在高IO的时候可以使用 例如爬虫, 爬虫需要请求很多url,使用协程可以让请求同时发出,而不会因为在等待一个url的请求响应而阻塞程序
不适用于高计算的环境, 因为在计算时cpu是一直工作的, 频繁的切换运行的程序,会白白增加切换程序的时间,导致计算效率下降