因爲GIL的存在,致使Python多線程性能甚至比單線程更糟。html
GIL: 全局解釋器鎖(英語:Global Interpreter Lock,縮寫GIL),是計算機程序設計語言解釋器用於同步線程的一種機制,它使得任什麼時候刻僅有一個線程在執行。[1]即使在多核心處理器上,使用 GIL 的解釋器也只容許同一時間執行一個線程。python
因而出現了協程(Coroutine)這麼個東西。git
協程: 協程,又稱微線程,纖程,英文名Coroutine。協程的做用,是在執行函數A時,能夠隨時中斷,去執行函數B,而後中斷繼續執行函數A(能夠自由切換)。但這一過程並非函數調用(沒有調用語句),這一整個過程看似像多線程,然而協程只有一個線程執行.express
協程因爲由程序主動控制切換,沒有線程切換的開銷,因此執行效率極高。對於IO密集型任務很是適用,若是是cpu密集型,推薦多進程+協程的方式。多線程
在Python3.4以前,官方沒有對協程的支持,存在一些三方庫的實現,好比gevent和Tornado。3.4以後就內置了asyncio標準庫,官方真正實現了協程這一特性。app
而Python對協程的支持,是經過Generator實現的,協程是遵循某些規則的生成器。所以,咱們在瞭解協程以前,咱們先要學習生成器。異步
咱們這裏主要討論yield
和yield from
這兩個表達式,這兩個表達式和協程的實現息息相關。async
方法中包含yield
表達式後,Python會將其視做generator對象,再也不是普通的方法。ide
yield
表達式的使用咱們先來看該表達式的具體使用:函數
def test():
print("generator start")
n = 1
while True:
yield_expression_value = yield n
print("yield_expression_value = %d" % yield_expression_value)
n += 1
# ①建立generator對象
generator = test()
print(type(generator))
print("\n---------------\n")
# ②啓動generator
next_result = generator.__next__()
print("next_result = %d" % next_result)
print("\n---------------\n")
# ③發送值給yield表達式
send_result = generator.send(666)
print("send_result = %d" % send_result)
複製代碼
執行結果:
<class 'generator'>
---------------
generator start
next_result = 1
---------------
yield_expression_value = 666
send_result = 2
複製代碼
方法說明:
__next__()
方法: 做用是啓動或者恢復generator的執行,至關於send(None)
send(value)
方法:做用是發送值給yield表達式。啓動generator則是調用send(None)
執行結果的說明:
①建立generator對象:包含yield表達式的函數將再也不是一個函數,調用以後將會返回generator對象
②啓動generator:使用生成器以前須要先調用__next__
或者send(None)
,不然將報錯。啓動generator後,代碼將執行到yield
出現的位置,也就是執行到yield n
,而後將n傳遞到generator.__next__()
這行的返回值。(注意,生成器執行到yield n
後將暫停在這裏,直到下一次生成器被啓動)
③發送值給yield表達式:調用send方法能夠發送值給yield表達式,同時恢復生成器的執行。生成器從上次中斷的位置繼續向下執行,而後遇到下一個yield
,生成器再次暫停,切換到主函數打印出send_result。
理解這個demo的關鍵是:生成器啓動或恢復執行一次,將會在yield
處暫停。上面的第②步僅僅執行到了yield n
,並無執行到賦值語句,到了第③步,生成器恢復執行纔給yield_expression_value
賦值。
上面的例子中,代碼中斷-->切換執行,體現出了協程的部分特色。
咱們再舉一個生產者、消費者的例子,這個例子來自廖雪峯的Python教程:
傳統的生產者-消費者模型是一個線程寫消息,一個線程取消息,經過鎖機制控制隊列和等待,但一不當心就可能死鎖。
如今改用協程,生產者生產消息後,直接經過
yield
跳轉到消費者開始執行,待消費者執行完畢後,切換回生產者繼續生產,效率極高。
def consumer():
print("[CONSUMER] start")
r = 'start'
while True:
n = yield r
if not n:
print("n is empty")
continue
print("[CONSUMER] Consumer is consuming %s" % n)
r = "200 ok"
def producer(c):
# 啓動generator
start_value = c.send(None)
print(start_value)
n = 0
while n < 3:
n += 1
print("[PRODUCER] Producer is producing %d" % n)
r = c.send(n)
print('[PRODUCER] Consumer return: %s' % r)
# 關閉generator
c.close()
# 建立生成器
c = consumer()
# 傳入generator
producer(c)
複製代碼
執行結果:
[CONSUMER] start
start
[PRODUCER] producer is producing 1
[CONSUMER] consumer is consuming 1
[PRODUCER] Consumer return: 200 ok
[PRODUCER] producer is producing 2
[CONSUMER] consumer is consuming 2
[PRODUCER] Consumer return: 200 ok
[PRODUCER] producer is producing 3
[CONSUMER] consumer is consuming 3
[PRODUCER] Consumer return: 200 ok
複製代碼
注意到
consumer
函數是一個generator
,把一個consumer
傳入produce
後:
- 首先調用
c.send(None)
啓動生成器;
- 而後,一旦生產了東西,經過
c.send(n)
切換到consumer執行;
consumer
經過yield
拿到消息,處理,又經過yield
把結果傳回;
produce
拿到consumer
處理的結果,繼續生產下一條消息;
produce
決定不生產了,經過c.close()
關閉consumer
,整個過程結束。
整個流程無鎖,由一個線程執行,
produce
和consumer
協做完成任務,因此稱爲「協程」,而非線程的搶佔式多任務。
yield from
表達式Python3.3版本新增yield from
語法,新語法用於將一個生成器部分操做委託給另外一個生成器。此外,容許子生成器(即yield from後的「參數」)返回一個值,該值可供委派生成器(即包含yield from的生成器)使用。而且在委派生成器中,可對子生成器進行優化。
咱們先來看最簡單的應用,例如:
# 子生成器
def test(n):
i = 0
while i < n:
yield i
i += 1
# 委派生成器
def test_yield_from(n):
print("test_yield_from start")
yield from test(n)
print("test_yield_from end")
for i in test_yield_from(3):
print(i)
複製代碼
輸出:
test_yield_from start
0
1
2
test_yield_from end
複製代碼
這裏咱們僅僅給這個生成器添加了一些打印,若是是正式的代碼中,你能夠添加正常的執行邏輯。
若是上面的test_yield_from
函數中有兩個yield from
語句,將串行執行。好比將上面的test_yield_from
函數改寫成這樣:
def test_yield_from(n):
print("test_yield_from start")
yield from test(n)
print("test_yield_from doing")
yield from test(n)
print("test_yield_from end")
複製代碼
將輸出:
test_yield_from start
0
1
2
test_yield_from doing
0
1
2
test_yield_from end
複製代碼
在這裏,yield from
起到的做用至關於下面寫法的簡寫形式
for item in test(n):
yield item
複製代碼
看起來這個yield from
也沒作什麼大不了的事,其實它還幫咱們處理了異常之類的。具體能夠看stackoverflow上的這個問題:In practice, what are the main uses for the new 「yield from」 syntax in Python 3.3?
@asyncio.coroutine
和yield from
實現協程async
/await
語法,參見PEP492咱們先來看Python3.4的實現。
@asyncio.coroutine
Python3.4中,使用@asyncio.coroutine
裝飾的函數稱爲協程。不過沒有從語法層面進行嚴格約束。
對裝飾器不瞭解的小夥伴能夠看個人上一篇博客--《理解Python裝飾器》
對於Python原生支持的協程來講,Python對協程和生成器作了一些區分,便於消除這兩個不一樣但相關的概念的歧義:
@asyncio.coroutine
裝飾器的函數稱爲協程函數,iscoroutinefunction()
方法返回Trueiscoroutine()
函數返回True舉個栗子,咱們給上面yield from
的demo中添加@asyncio.coroutine
:
import asyncio
...
@asyncio.coroutine
def test_yield_from(n):
...
# 是不是協程函數
print(asyncio.iscoroutinefunction(test_yield_from))
# 是不是協程對象
print(asyncio.iscoroutine(test_yield_from(3)))
複製代碼
毫無疑問輸出結果是True。
能夠看下@asyncio.coroutine
的源碼中查看其作了什麼,我將其源碼簡化下,大體以下:
import functools
import types
import inspect
def coroutine(func):
# 判斷是不是生成器
if inspect.isgeneratorfunction(func):
coro = func
else:
# 將普通函數變成generator
@functools.wraps(func)
def coro(*args, **kw):
res = func(*args, **kw)
res = yield from res
return res
# 將generator轉換成coroutine
wrapper = types.coroutine(coro)
# For iscoroutinefunction().
wrapper._is_coroutine = True
return wrapper
複製代碼
將這個裝飾器標記在一個生成器上,就會將其轉換成coroutine。
而後,咱們來實際使用下@asyncio.coroutine
和yield from
:
import asyncio
@asyncio.coroutine
def compute(x, y):
print("Compute %s + %s ..." % (x, y))
yield from asyncio.sleep(1.0)
return x + y
@asyncio.coroutine
def print_sum(x, y):
result = yield from compute(x, y)
print("%s + %s = %s" % (x, y, result))
loop = asyncio.get_event_loop()
print("start")
# 中斷調用,直到協程執行結束
loop.run_until_complete(print_sum(1, 2))
print("end")
loop.close()
複製代碼
執行結果:
start
Compute 1 + 2 ...
1 + 2 = 3
end
複製代碼
print_sum
這個協程中調用了子協程compute
,它將等待compute
執行結束才返回結果。
這個demo點調用流程以下圖:
EventLoop將會把print_sum
封裝成Task對象
流程圖展現了這個demo的控制流程,不過沒有展現其所有細節。好比其中「暫停」的1s,實際上建立了一個future對象, 而後經過BaseEventLoop.call_later()
在1s後喚醒這個任務。
值得注意的是,@asyncio.coroutine
將在Python3.10版本中移除。
async
/await
Python3.5開始引入async
/await
語法(PEP 492),用來簡化協程的使用而且便於理解。
async
/await
實際上只是@asyncio.coroutine
和yield from
的語法糖:
@asyncio.coroutine
替換爲async
yield from
替換爲await
便可。
好比上面的例子:
import asyncio
async def compute(x, y):
print("Compute %s + %s ..." % (x, y))
await asyncio.sleep(1.0)
return x + y
async def print_sum(x, y):
result = await compute(x, y)
print("%s + %s = %s" % (x, y, result))
loop = asyncio.get_event_loop()
print("start")
loop.run_until_complete(print_sum(1, 2))
print("end")
loop.close()
複製代碼
咱們再來看一個asyncio中Future的例子:
import asyncio
future = asyncio.Future()
async def coro1():
print("wait 1 second")
await asyncio.sleep(1)
print("set_result")
future.set_result('data')
async def coro2():
result = await future
print(result)
loop = asyncio.get_event_loop()
loop.run_until_complete(asyncio.wait([
coro1()
coro2()
]))
loop.close()
複製代碼
輸出結果:
wait 1 second
(大約等待1秒)
set_result
data
複製代碼
這裏await後面跟隨的future對象,協程中yield from或者await後面能夠調用future對象,其做用是:暫停協程,直到future執行結束或者返回result或拋出異常。
而在咱們的例子中,await future
必需要等待future.set_result('data')
後纔可以結束。將coro2()
做爲第二個協程可能體現得不夠明顯,能夠將協程的調用改爲這樣:
loop = asyncio.get_event_loop()
loop.run_until_complete(asyncio.wait([
# coro1(),
coro2(),
coro1()
]))
loop.close()
複製代碼
輸出的結果仍舊與上面相同。
其實,async
這個關鍵字的用法不止能用在函數上,還有async with
異步上下文管理器,async for
異步迭代器. 對這些感興趣且以爲有用的能夠網上找找資料,這裏限於篇幅就不過多展開了。
本文就生成器和協程作了一些學習、探究和總結,不過並無作過多深刻深刻的研究。權且做爲入門到一個筆記,以後將會嘗試本身實現一下異步API,但願有助於理解學習。
Python協程 https://thief.one/2017/02/20/Python%E5%8D%8F%E7%A8%8B/