asyncio
該模塊是3.4版本加入的新功能。
先來看一個例子:
def a(): for x in range(3): print('a.x', x) def b(): for x in 'abc': print('b.x', x) a() b() #運行結果: a.x 0 a.x 1 a.x 2 b.x a b.x b b.x c
這個例子是一個典型的串行程序,兩個函數調用是在主線程中順序執行。
有以下幾種方法可以讓這段程序改為並行:
1. 生成器
2. 多線程
3. 多進程
4. 協程
1)生成器方法:
def a(): for x in range(3): yield x def b(): for x in 'abc': yield x m = a() n = b() for _ in range(3): print(next(m)) print(next(n)) #運行結果: 0 a 1 b 2 c
使用生成器來實現交替執行。這兩個函數都有機會執行,這樣的調度不是操作系統的進程、線程完成的,而是用戶自己設計的。
2)多線程方法:
import threading,time def a(): for x in range(3): time.sleep(0.0001) print('a.x',x) def b(): for x in 'abc': time.sleep(0.0001) print('b.x',x) threading.Thread(target=a).start() threading.Thread(target=b).start() #運行結果: a.x 0 b.x a a.x 1 b.x b a.x 2 b.x c
主要使用sleep函數強制切換來實現偽並行。
3)多進程方式:
import multiprocessing def a(): for x in range(3): print('a.x',x) def b(): for x in 'abc': print('b.x',x) if __name__ == '__main__': multiprocessing.Process(target=a).start() multiprocessing.Process(target=b).start() #運行結果: a.x 0 a.x 1 a.x 2 b.x a b.x b b.x c
多進程方式才是真正的並行。
4)協程方法:
協程,需要使用到 asyncio 標准庫,是Python3.4版本加入的新功能,底層基於selectors實現,包括異步IO、事件循環、協程等內容。
事件循環:
事件循環是asyncio提供的核心運行機制。
程序開啟一個無限的循環,使用者會把一些函數注冊到事件循環上。當滿足事件發生的時候,調用相應的協程函數。
4.1 事件循環基類
asyncio.BaseEventLoop 這個類是一個實現細節,它是asyncio.AbstractEventLoop的子類,不可以直接使用
asyncio.AbstractEventLoop 事件循環的抽象基類,這個類是是線程不安全的
4.2 運行事件循環
asyncio.get_event_loop() 返回一個事件循環對象,是asyncio.BaseEventLoop的實例
asyncio.AbstractEventLoop.stop() 停止運行事件循環
asyncio.AbstractEventLoop.run_forever() 一直運行,直到調用stop()
asyncio.AbstractEventLoop.run_until_complete(future) 運行直到future對象運行完成,返回結果
asyncio.AbstractEventLoop.close() 關閉事件循環
asyncio.AbstractEventLoop.is_running() 返回事件循環的運行狀態
asyncio.AbstractEventLoop.is_closed() 如果事件循環已關閉,返回True
4.3 協程
協程不是進程、也不是線程,它是用戶空間調度完成並發處理的方式。(同一線程內交替執行其實也是偽並發)
並發指的是在一段時間內做了多少、並行指的是同一時刻有多少同時執行。
進程、線程由操作系統完成調度,而協程是線程內完成調度。它不需要更多的線程,也就沒有多線程切換帶來的開銷。
協程是非搶占式調度,只有一個協程主動讓出控制權,另一個協程才會被調度。
協程也不需要鎖機制,因為是在同一線程中執行。
多CPU下,可以使用多線程和協程配合,既能進程並發又能發揮協程在單線程中的優勢。
Python中協程是基於生成器的。
4.4 協程的使用
4.4.1 Python3.4中使用@asyncio.coroutine 、 yield from
#asyncio Python3.4 import asyncio @asyncio.coroutine def foo(x): #生成器函數上面加了協程裝飾器之后就轉化成協程函數 for i in range(3): print('foo {}'.format(i)) yield from asyncio.sleep(x) #調用另一個生成器對象 loop = asyncio.get_event_loop() #獲得一個時間循環 loop.run_until_complete(foo(1)) #傳入一個生成器對象的調用 loop.close() #運行結果: foo 0 foo 1 foo 2 [Finished in 3.3s]
此例子在一個生成器函數加了協程裝飾器之后,該生成器函數就轉化成了協程函數。
4.4.2 Python3.5中使用關鍵字 async def 、 await ,在語法上原生支持協程
#asyncio Python3.5 import asyncio async def foo(x): #異步定義,協程定義 for i in range(3): print('foo {}'.format(i)) await asyncio.sleep(x) #不可以出現yield,使用await替換 print(asyncio.iscoroutinefunction(foo)) loop = asyncio.get_event_loop() loop.run_until_complete(foo(1)) #傳入一個協程對象的調用 loop.close() #運行結果: True foo 0 foo 1 foo 2 [Finished in 3.3s]
async def 用來定義協程函數,iscoroutinefunction(func)判斷func函數是否是一個協程函數。協程函數中可以不包含await、async關鍵字,但不能使用yield關鍵字。
其它語法:async with,支持上下文的協程
4.4.3 coroutine asyncio.wait(futures, *, loop=None, timeout=None, return_when=ALL_COMPLETED)
等待futures序列中的協程對象執行完成,futures序列不可以為空。
timeout可以用於控制返回前等待的最大秒數,秒數可以是int或浮點數,如果未指定timeout,則無限制。
#wait多個協程對象 import asyncio @asyncio.coroutine def a(): for i in range(3): print('a.x',i) yield @asyncio.coroutine def b(): for i in range(3): print('b.x',i) yield loop = asyncio.get_event_loop() tasks = [a(),b()] loop.run_until_complete(asyncio.wait(tasks)) #傳入一個協程對象序列 loop.close() #運行結果: b.x 0 a.x 0 b.x 1 a.x 1 b.x 2 a.x 2 [Finished in 0.3s]
總結:
傳統的多線程、多進程都是系統完成調度,而協程是在進程中的線程內由用戶空間調度完成並發處理,主要依靠生成器來實現交替調度。
Python3.4中使用@asyncio.coroutine、yield from調用另一個生成器對象
Python3.5中使用關鍵字 async def 和 await,且不可以出現yield關鍵字。