协程
来源:互联网 发布:汽车行业制图软件 编辑:程序博客网 时间:2024/06/05 10:04
- 概念
- 协程的yield实现
- greenlet模块
- gevent
- asyncio 异步模块
- 基本使用
- 手动封装报头
- aiohttp模块封装报头
- requests模块 asyncio
概念
无论是多进程还是多线程,在遇到IO阻塞时都会被操作系统强行剥夺走CPU的执行权限,程序的执行效率因此就降低了下来。解决这一问题的关键在于,我们自己从应用程序级别检测IO阻塞然后切换到我们自己程序的其他任务执行,这样把我们程序的IO降到最低,我们的程序处于就绪态就会增多,以此来迷惑操作系统,操作系统便以为我们的程序是IO比较少的程序,从而会尽可能多的分配CPU给我们,这样也就达到了提升程序执行效率的目的。这样,就用到了协程。
协程:是单线程下的并发,又称微线程,纤程。英文名Coroutine。一句话说明什么是线程:协程是一种用户态的轻量级线程,即协程是由用户程序自己控制调度的。
需要强调的是:
- python的线程属于内核级别的,即由操作系统控制调度(如单线程一旦遇到io就被迫交出cpu执行权限,切换其他线程运行)
- 单线程内开启协程,一旦遇到io,从应用程序级别(而非操作系统)控制切换对比操作系统控制线程的切换,用户在单线程内控制协程的切换,优点如下:
- 协程的切换开销更小,属于程序级别的切换,操作系统完全感知不到,因而更加轻量级
- 单线程内就可以实现并发的效果,最大限度地利用cpu
协程的yield实现
要实现协程,关键在于用户程序自己控制程序切换,切换之前必须由用户程序自己保存协程上一次调用时的状态,如此,每次重新调用时,能够从上次的位置继续执行。我们之前学的yield生成器函数其实就可以实现协程的效果。
下面我们用yield来写一个生产者消费者模型:
import timedef consumer(): res = '' while True: n = yield res if not n: return print('[顾客] <-- 消费:',n) time.sleep(1) res = '好吃,再来...'def produce(c): next(c) # 初始化生成器 n = 0 while n < 5: n += 1 print('[厨师] --> 生产:','包子%s'% n) res = c.send('包子%s'% n) print('[厨师] 顾客反馈:',res) c.close() # 关闭生成器if __name__ == '__main__': c = consumer() produce(c)
传统的生产者-消费者模型是一个线程写消息,一个线程取消息,通过锁机制控制队列和等待,但一不小心就可能死锁。
如果改用协程,生产者生产消息后,直接通过yield跳转到消费者开始执行,待消费者执行完毕后,切换回生产者继续生产。这样也实现了并发,不依赖于多线程或多进程,并且几乎没有切换消耗。
greenlet模块
该模块封装了yield,两个函数的切换也更方便。
from greenlet import greenlet import timedef foo(): print('foo,step1') time.sleep(2) # 程序将阻塞在这里,然后再切换到bar函数 gr2.switch() print('foo,step2')def bar(): print('bar,step1') gr1.switch() # 切换到foo函数,从foo上次switch()暂停处继续执行 print('bar,step2')gr1 = greenlet(foo) # 创建对象gr2 = greenlet(bar)gr1.switch() # 哪个函数先运行就启动对应的对象'''foo,step1bar,step1foo,step2bar,step2'''
协程的切换不会节省时间。和顺序执行是一样的。必须解决I/O密集型任务的性能问题。gevent模块可以解决这个问题。
gevent
gevent是一个第三方库,基于greenlet实现协程,可以实现并发:
当一个greenlet遇到IO操作时,比如访问网络,就自动切换到其他的greenlet,等到IO操作完成,再在适当的时候切换回来继续执行。由于IO操作非常耗时,经常使程序处于等待状态,有了gevent为我们自动切换协程,就保证总有greenlet在运行,而不是等待IO。遇到I/O自动切换,其实是基于I/O模型的IO multiplexing
用法如下:
在导入模块的最上面打补丁:
from gevent import monkey; monkey.patch_all()
import gevent
说明:切换是在IO操作时自动完成,所以gevent需要修改Python自带的一些标准库,这一过程在启动时通过monkey patch_all()完成。
创建对象:
obj1 = gevent.spawn(func)
obj2 = gevent.spawn(func)
…
放入对象:
gevent.joinall([obj1, obj2])
from gevent import monkey; monkey.patch_all()import geventimport timedef eat(): print('吃饼1') time.sleep(3) # 等待上饼 print('吃饼2')def work(): print('搬第一车砖') time.sleep(4) # 等第二车砖 print('搬第二车砖')if __name__ == '__main__': start = time.time() g1 = gevent.spawn(eat) g2 = gevent.spawn(work) gevent.joinall([g1, g2]) print('一共耗时:',time.time()-start)''' 执行结果吃饼1搬第一车砖吃饼2搬第二车砖一共耗时: 4.004263162612915'''
from gevent import monkey; monkey.patch_all()import geventimport requestsimport timedef get_page(url): print('GET: %s' % url) resp = requests.get(url) print('%d bytes received from %s.' % (len(resp.text), url)) return resp.textstart=time.time()g1=gevent.spawn(get_page,'https://www.python.org/doc')g2=gevent.spawn(get_page,'https://www.github.com/')g3=gevent.spawn(get_page,'http://www.csdn.com/')gevent.joinall([g1,g2,g3,])print(g3.value) # .value拿到返回值print(time.time()-start)# gevent 协程池from gevent import monkey; monkey.patch_all()import geventimport requestsimport timedef get_page(url): print('GET: %s' % url) resp = requests.get(url) print('%d bytes received from %s.' % (len(resp.text), url)) return resp.textfrom gevent.pool import Poolstart = time.time()pool = Pool(3) # 实例化协程池;测试发现如果池大小为1,那么执行完一个任务就结束了,其它没问题tasks = [ 'https://www.python.org/doc', 'https://www.github.com/', 'http://www.csdn.com/', 'http://www.cnblog.com', 'http://www.douban.com',]for url in tasks: pool.apply_async(get_page, args=(url,))pool.join()print(time.time() - start)
asyncio 异步模块
基本使用
该模块是python3.3之后新增的,可以帮我们检测IO(只能是网络IO),实现应用程序级别的切换。下面我们看一下基本使用:
import asyncioimport time@asyncio.coroutinedef get_html(url): print('开始请求%s' % url) yield from asyncio.sleep(random.randint(1, 3)) # yield from 检测IO, 保存状态;这里用asyncio.sleep模拟网络IO return '%s 返回 html......' % urlstart = time.time()urls = ['url_1', 'url_2', 'url_3', 'url_4', 'url_5', 'url_6']tasks = []for url in urls: tasks.append(get_html(url))loop = asyncio.get_event_loop()loop.run_until_complete(asyncio.gather(*tasks))loop.close()end = time.time()print('一共耗时:', end - start)
手动封装报头
不过asyncio模块只能发送tcp包(只封装到这一层),不能直接发http包,因此,发送http请求时,需要额外封装http协议头。
import asyncioimport time@asyncio.coroutinedef get_html(host, post=80, url='/'): print('开始请求:',host) # step1: 建立tcp连接,IO阻塞 conn_recv, conn_send = yield from asyncio.open_connection(host, post) # step2: 封装http报头 header = """GET {url} HTTP/1.0\r\nHOST:{host}\r\n...\r\n\r\n"""\ .format(url=url, host=host).encode('utf-8') # step3: 发送http请求,IO阻塞 conn_send.write(header) yield from conn_send.drain() # Flush the write buffer # step4 接收http请求, IO阻塞 html = yield from conn_recv.read() # 关闭通道 conn_send.close() print(host, url, len(html.decode('utf-8')))start = time.time()urls = ['url_1', 'url_2', 'url_3', 'url_4', 'url_5', 'url_6']tasks = [ get_html(host='www.zhihu.com'), get_html(host='blog.csdn.net',url='//ayhan_huang'), get_html(host='www.sogou.com')]loop = asyncio.get_event_loop()loop.run_until_complete(asyncio.gather(*tasks))loop.close()end = time.time()print('一共耗时:', end - start)"""开始请求: www.zhihu.com开始请求: www.sogou.com开始请求: blog.csdn.netwww.sogou.com / 558www.zhihu.com / 488blog.csdn.net //ayhan_huang 41957一共耗时: 1.0431559085845947"""
aiohttp模块封装报头
手动封装报头过于繁琐,借助aiohttp模块,可以简化我们的工作:
import asyncioimport aiohttpimport time@asyncio.coroutinedef get_html(url): print('开始请求:',url) # IO http请求 response = yield from aiohttp.request('GET',url) # IO http响应 data = yield from response.read() print(url, len(data))start = time.time()tasks = [ get_html('http://www.zhihu.com'), get_html('http://blog.csdn.net/ayhan_huang'), get_html('http://www.sogou.com')]loop = asyncio.get_event_loop()loop.run_until_complete(asyncio.gather(*tasks))loop.close()end = time.time()print('一共耗时:', end - start)
requests模块 + asyncio
将requests模块的函数作为参数传入,可以以我们熟悉的方式发起http请求
import asyncioimport requestsimport time@asyncio.coroutinedef get_html(func, *args): print('开始请求:',*args[0]) loop = asyncio.get_event_loop() future = loop.run_in_executor(None, func, *args) response = yield from future print(response.url, len(response.text))start = time.time()tasks = [ get_html(requests.get, 'http://www.chouti.com'), get_html(requests.get, 'http://blog.csdn.net/ayhan_huang'), get_html(requests.get, 'http://www.sogou.com')]loop = asyncio.get_event_loop()loop.run_until_complete(asyncio.gather(*tasks))loop.close()end = time.time()print('一共耗时:', end - start)
- 协程
- 协程
- 协程
- 协程
- 协程
- 协程
- 协程
- 协程
- 协程
- 协程
- 协程
- 协程
- 协程
- 协程
- 协程
- 协程
- 协程
- 协程
- 自用-动画管理
- Arrays.sort()用的是什么排序算法?怎么优化?
- 51nod1008N的阶乘
- 关于mysql中where条件的类型自动转换
- ZYNQ XC7Z030平台Linux+裸机AMP实现(官方文档1078、1079)
- 协程
- WebStrom 自动补全快捷键
- Mac显示隐藏文件命令
- This is usually caused by another repository pushing:refusing to merge unrelated histories
- c#笔记(二)
- Reactor模式软文
- js动态改变地址栏url,不刷新页面
- java死锁的原因分析及解锁和预防措施
- mysql事务