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關鍵字。
