協程簡單來講就是一個更加輕量級的線程,而且不禁操做系統內核管理,徹底由程序所控制(在用戶態執行)。協程在子程序內部是可中斷的,而後轉而執行其餘子程序,在適當的時候返回過來繼續執行。html
協程的優點?(協程擁有本身的寄存器上下文和棧,調度切換時,寄存器上下文和棧保存到其餘地方,在切換回來的時候,恢復先前保存的寄存器上下文和棧,直接操做棧則基本沒有內核切換的開銷,能夠不加鎖的訪問全局變量,因此上下文很是快。)python
一、協程中的yield一般出如今表達式的右邊:web
x = yield data
若是yield的右邊沒有表達式,默認產出的值是None,如今右邊有表達式,因此返回的是data這個值。
二、協程能夠從調用法接受數據,調用經過send(x)方式將數據提供給協程,同時send方法中包含next方法,因此程序會繼續執行。
三、協程能夠中斷執行,去執行另外的協程。編程
代碼:服務器
def hello(): data = "mima" while True: x = yield data print(x) a = hello() next(a) data = a.send("hello") print(data)
代碼詳解:
程序開始執行,函數hello不會真的執行,而是返回一個生成器給a。
當調用到next()方法時,hello函數纔開始真正執行,執行print方法,繼續進入while循環;
程序遇到yield關鍵字,程序再次中斷,此時執行到a.send("hello")時,程序會從yield關鍵字繼續向下執行,而後又再次進入while循環,再次遇到yield關鍵字,程序再次中斷;網絡
額外session
協程在運行過程當中的四個狀態:併發
- GEN_CREATE:等待開始執行
- GEN_RUNNING:解釋器正在執行
- GEN_SUSPENDED:在yield表達式處暫停
- GEN_CLOSED:執行結束
import time def consumer(): r = "" while True: res = yield r if not res: print("Starting.....") return print("[CONSUMER] Consuming %s...." %res) time.sleep(1) r = "200 OK" def produce(c): next(c) n = 0 while n<6: n+=1 print("[PRODUCER] Producing %s ...."%n) r = c.send(n) print("[CONSUMER] Consumer return: %s ...."%r) c.close() c = consumer() produce(c)
代碼分析:app
- 調用next(c)啓動生成器;
- 消費者一旦生產東西,經過c.send切換到消費者consumer執行;
- consumer經過yield關鍵字獲取到消息,在經過yield把結果執行;
- 生產者拿到消費者處理過的結果,繼續生成下一條消息;
- 當跳出循環後,生產者不生產了,經過close關閉消費者,整個過程結束;
原理:gevent基於協程的Python網絡庫,當一個greenlet遇到IO操做(訪問網絡)自動切換到其餘的greenlet等到IO操做完成後,在適當的時候切換回來繼續執行。換而言之就是greenlet經過幫咱們自動切換協程,保證有greenlet在運行,而不是一直等待IO操做。框架
因爲切換時在發生IO操做時自動完成,因此gevent須要修改Python內置庫,這裏能夠打上猴子補丁(用來在運行時動態修改已有的代碼,而不須要原有的代碼)monkey.patch_all
#!/usr/bin/python2 # coding=utf8 from gevent import monkey monkey.patch_all() import gevent import requests def handle_html(url): print("Starting %s。。。。" % url) response = requests.get(url) code = response.status_code print("%s: %s" % (url, str(code))) if __name__ == "__main__": urls = ["https://www.baidu.com", "https://www.douban.com", "https://www.qq.com"] jobs = [ gevent.spawn(handle_html, url) for url in urls ] gevent.joinall(jobs)
運行結果:
結果:3個網絡鏈接併發執行,可是結束的順序不一樣。
原理:asyncio的編程模型就是一個消息循環,從asyncio模塊中直接獲取一個Eventloop(事件循環)的應用,而後把須要執行的協程放入EventLoop中執行,實現異步IO。
經典代碼:
import asyncio import threading async def hello(): print("hello, world: %s"%threading.currentThread()) await asyncio.sleep(1) # print('hello, man %s'%threading.currentThread()) if __name__ == "__main__": loop = asyncio.get_event_loop() loop.run_until_complete(asyncio.wait([hello(), hello()])) loop.close()
代碼解析:
- 首先獲取一個EventLoop
- 而後將這個hello的協程放進EventLoop,運行EventLoop,它會運行知道future被完成
- hello協程內部執行await asyncio.sleep(1)模擬耗時1秒的IO操做,在此期間,主線程並未等待,而是去執行EventLoop中的其餘線程,實現併發執行。
代碼結果:
異步爬蟲實例:
#!/usr/bin/python3 import aiohttp import asyncio async def fetch(url, session): print("starting: %s" % url) async with session.get(url) as response: print("%s : %s" % (url,response.status)) return await response.read() async def run(): urls = ["https://www.baidu.com", "https://www.douban.com", "http://www.mi.com"] tasks = [] async with aiohttp.ClientSession() as session: tasks = [asyncio.ensure_future(fetch(url, session)) for url in urls] # 建立任務 response = await asyncio.gather(*tasks) # 併發執行任務 for body in response: print(len(response)) if __name__ == "__main__": loop = asyncio.get_event_loop() loop.run_until_complete(run()) loop.close()
代碼解析:
- 建立一個事件循環,而後將任務放到時間循環中;
- run()方法中主要是建立任務,併發執行任務,返回讀取到的網頁內容;
- fetch()方法經過aiohttp發出指定的請求,以及返回 可等待對象;
(結束輸出網址和list中網址的順序不一樣,證實協程中異步I/O操做)
asyncio實現類TCP、UDP、SSL等協議,aiohttp則是基於asyncio實現的HTTP框架,由此能夠用來編寫一個微型的HTTP服務器。
代碼:
from aiohttp import web async def index(request): await asyncio.sleep(0.5) print(request.path) return web.Response(body='<h1> Hello, World</h1>') async def hello(request): await asyncio.sleep(0.5) text = '<h1>hello, %s</h1>'%request.match_info['name'] print(request.path) return web.Response(body=text.encode('utf-8')) async def init(loop): app = web.Application(loop=loop) app.router.add_route("GET", "/" , index) app.router.add_route("GET","/hello/{name}", hello) srv = await loop.create_server(app.make_handler(), '127.0.0.1', 8000) print("Server started at http://127.0.0.0.1:8000....") return srv if __name__ == "__main__": loop = asyncio.get_event_loop() loop.run_until_complete(init(loop)) loop.run_forever()
代碼解析:
- 建立一個事件循環,傳入到init協程中;
- 建立Application實例,而後添加路由處理指定的請求;
- 經過loop建立TCP服務,最後啓動事件循環;
https://www.liaoxuefeng.com/wiki/1016959663602400/1017985577429536
https://docs.aiohttp.org/en/stable/web_quickstart.html
https://docs.python.org/zh-cn/3.7/library/asyncio-task.html