這是python3.4引入的標準庫,直接內置對異步IO的支持。asyncio的編程模型就是一個消息循環。從asyncio
模塊中直接獲取一個EventLoop
的引用,而後把須要執行的協程扔到EventLoop
中執行,就實現了異步IO。python
子程序,或者稱爲函數。在全部語言中都是層級調用,好比A調用B,B在執行過程當中又調用了C,C執行完畢返回,B執行完畢返回,最後是A執行完畢。子程序調用是經過棧實現的,一個線程就是執行一個子程序。子程序調用老是一個入口,一次返回,調用順序是明確的。而協程的調用和子程序不一樣。編程
協程看上去是子程序,可是有很大的不一樣。協程,執行過程當中,在內部可中斷,而後轉而執行別的程序,在適當的時候再返回來接着執行。在一個子程序中中斷,去執行其餘子程序,不是函數調用,有點相似CPU的中斷。下面舉個例子:網絡
def A(): print('1') print('2') print('3') def B(): print('x') print('y') print('z')
假設由協程執行,在執行A的過程當中,能夠隨時中斷,去執行B,B也可能在執行過程當中中斷再去執行A,結果多是:多線程
1 2 x y 3 z
可是在A中是沒有調用B的,因此不是函數的調用中斷。所以,協程的調用比函數調用理解起來要難一些。併發
協程最大的優點就是極高的執行效率。由於子程序切換不是線程切換,而是由程序自身控制,所以,沒有線程切換的開銷。第二大優點就是不須要多線程的鎖機制,由於只有一個線程,也不存在同時寫變量衝突,在協程中控制共享資源不加鎖,只須要判斷狀態就行了,因此執行效率比多線程高不少。異步
協程利用多個CPU的方法是,多進程+協程,既充分利用多核,又充分發揮協程的高效率,可得到極高的性能。socket
Python對協程的支持是經過generator(帶有yield的函數)實現的。在generator中,咱們不但能夠經過for
循環來迭代,還能夠不斷調用next()
函數獲取由yield
語句返回的下一個值。Python的yield
不但能夠返回一個值,它還能夠接收調用者發出的參數。看個實例:async
def consumer(): r = 'k' # 定義一個空字符串 while True: # 設定一個循環 # 若是不調用consumer的send方法傳入其參數給n,n將爲None n = yield r if not n: # 若是知足條件,表示方法外並未調用send return # 執行return,退出方法,返回空值 print('[CONSUMER] Consuming %s...' % n) r = '200 OK' def produce(c): x = c.send(None) # 啓動生成器,至關於調用了next(c) print("This is qidong:", x) n = 0 while n < 5: n = n + 1 print('[PRODUCER] Producing %s...' % n) # 生產了東西,經過c.send(n)切換到consumer執行 r = c.send(n) # consumer經過yield拿到消息,處理,又經過yield把結果傳回 print('[PRODUCER] Consumer return: %s' % r) # produce拿到consumer處理的結果,繼續生產下一條消息 # produce決定不生產了,經過c.close()關閉consumer,整個過程結束 c.close() c = consumer() produce(c)
從實例能夠看出來,整個生產者消費者的流程是無鎖的,只由一個線程執行,produce
和consumer
協做完成任務,因此稱爲「協程」,而非線程的搶佔式多任務。函數
下面是用asyncio
實現Hello world:
oop
import asyncio # @asyncio.coroutine把一個generator標記爲coroutine(協程)類型 @asyncio.coroutine def hello(): print("Hello world!") # 異步調用asyncio.sleep(1): # yield from語法可讓咱們方便地調用另外一個generator # asyncio.sleep()也是一個coroutine # 線程不會等待asyncio.sleep(),而是直接中斷並執行下一個消息循環 # 把asyncio.sleep(1)當作是一個耗時1秒的IO操做,在此期間, # 主線程並未等待,而是去執行EventLoop中其餘能夠執行的coroutine了,所以能夠實現併發執行 yield from asyncio.sleep(1) print("Hello again!") # 從asyncio模塊中直接獲取EventLoop loop = asyncio.get_event_loop() # 執行coroutine loop.run_until_complete(hello()) loop.close()
用Task封裝兩個coroutine
試試:
import asyncio import threading @asyncio.coroutine def hello(): print('Hello world! (%s)' % threading.currentThread()) yield from asyncio.sleep(1) print('Hello again! (%s)' % threading.currentThread()) # 從asyncio模塊中直接獲取EventLoop loop = asyncio.get_event_loop() tasks = [hello(), hello()] loop.run_until_complete(asyncio.wait(tasks)) loop.close()
運行結果是:
Hello world! (<_MainThread(MainThread, started 7116)>) Hello world! (<_MainThread(MainThread, started 7116)>) Hello again! (<_MainThread(MainThread, started 7116)>) Hello again! (<_MainThread(MainThread, started 7116)>)
能夠看出來,兩個協程是由同一個線程併發執行的。把asyncio.sleep()
換成真正的IO操做,則多個coroutine
就能夠由一個線程併發執行。
用asyncio
的異步網絡鏈接來獲取sina、sohu和163的網站首頁:
import asyncio @asyncio.coroutine def wget(host): print('wget %s...' % host) connect = asyncio.open_connection(host, 80) reader, writer = yield from connect header = 'GET / HTTP/1.0\r\nHost: %s\r\n\r\n' % host writer.write(header.encode('utf-8')) # drain()在事件循環中刷新緩衝區,特別是在數據量很大的狀況下,保證數據完整性 yield from writer.drain() while True: line = yield from reader.readline() if line == b'\r\n': break print('%s header > %s' % (host, line.decode('utf-8').rstrip())) # Ignore the body, close the socket writer.close() loop = asyncio.get_event_loop() tasks = [wget(host) for host in ['www.sina.com.cn', 'www.sohu.com', 'www.163.com']] loop.run_until_complete(asyncio.wait(tasks)) loop.close()
可見3個鏈接由一個線程經過coroutine
併發完成。
asyncio模塊
提供了完善的異步IO支持。異步操做須要在coroutine(協程)
中經過yield from
完成。多個coroutine
能夠封裝成一組Task而後併發執行。
注意在python3.5以後,能夠把@asyncio.coroutine替換爲async,yield from替換爲await。使代碼更簡潔易懂。