![協程在python中如何使用](http://p2.ttnews.xyz/loading.gif)
什麼是協程
協程,又稱為微線程,纖程,英文名Coroutine。協程的作用是在執行函數A時,可以隨時中斷,去執行函數B,然後中斷繼續執行函數A(可以自由切換)。但這一過程並不是函數調用(沒有調用語句),這一整個過程看似像多線程,然而協程只有一個線程執行。
優點
- 執行效率極高,因為子程序切換(函數)不是線程切換,由程序自身控制,沒有切換線程的開銷。所有與多線程相比,線程的數量越多,協程性能的優勢越明顯。
- 不需要多線程的鎖機制,因為只有一個線程,也不存在同事寫變量衝突,在控制共享資源時也不需要枷鎖,因此執行效率高很多。
python 2.x的協程
主要應用:
yield和geventpython 2.x中支持協程的模塊不多,常用的就是gevent。我們主要講解gevent的用法。
Gevent
gevent是第三方庫,通過greenlet實現協程,其基本思想:當一個greenlet遇到IO操作時,比如訪問網絡,就自動切換到其他的greenlet,等到IO操作完成,再在適當的時候切換回來繼續執行。由於IO操作非常好使,經常是程序處於等到狀態,有了gevent為我們自動切換協程,就爆炸總有greenlet在運行,而不是等待IO。
gevent實現了python標準庫裡面大部分的阻塞式系統調用,包括socket、ssl、threading和select等模塊,而將這些阻塞式調用變為協作式運行。通過monkey patch修改python自帶的一些標準庫。例子:
<code>from gevent import monkey
monkey.patch_all() # 一定要在所有引入模塊之前執行
import gevent
import reqeusts
def get_response(pn):
print('start', pn)
requests.get('https://www.baidu.com')
print('end', pn)
tasks = [gevent.spawn(get_response, pn) for pn in range(5)]
gevent.joinall(tasks)/<code>
Gevent的方法:monkey使一些阻塞的模塊變得不阻塞,遇到IO操作則自動切換
- gevent.sleep 可以手動切換
- gevent.spawn 啟動協程,參數為(func_name,args)
- gevent.joinall 阻塞當前流程,並執行所有給定的greenlet。執行流程只會在所有greenlet執行完後才會繼續向下走
python 3.x的協程
python對協程的支持是通過生成器實現的。在生成器中,我們不但可以通過for循環來迭代,還可以不斷調用next()函數獲取由yield語句返回的下一個值。但是python的yield不但可以返回一個值,它還可以接收調用者發出的參數。來看例子:
<code>def consumer():
r = ''
while True:
n = yield r
if not n:
return
print('Consuming %s...' % n)
r = '200 OK'
def produce(c):
c.send(None) #啟動生成器,沒有返回值
n = 0
while n n += 1
print('Producing %s...' % n)
r = c.send(n)
print('Consumer return: %s' % r)
c.close()
c = consumer()
produce(c)/<code>
注意到consumer函數是一個生成器,把一個consumer傳入produce後:
- 調用c.send(None)啟動生成器
- 一旦生產了東西,通過c.send(n)切換到consumer執行
- consumer通過yield拿到消息,處理,又通過yield把結果傳回
- produce拿到consumer處理的結果,繼續生產下一條消息
- produce決定不生產了,通過c.close()關閉consumer,整個過程結束
整個流程無鎖,由一個線程執行,produce和consumer協作完成任務,所以稱為“協程”,而非線程的搶佔式多任務。
上面只是協程的簡單操作,python3中對協程的編程模型引入了一些標準庫。python3.4以後引入了asyncio模塊,可以很好的支持協程,asyncio的編程模型就是一個小小循環。我們從asyncio模塊中直接獲取一個EventLoop的引用,然後把需要執行的協程扔到EventLoop中執行,就實現了異步IO。
<code>import asyncio
@asyncio.coroutine
def hello():
print('Hello World!')
# 異步調用asyncio.sleep(1)
r = yield from asyncio.sleep(2)
print('Hello again!')
# 獲取EventLeep
loop = asyncio.get_event_loop()
# 執行coroutine
loop.run_until_complete(hello())
loop.close()/<code>
asyncio說明
- @asyncio.coroutine把一個生成器標記為coroutine類型,然後我們就把這個coroutine扔到EventLoop中執行
- hello()會首先答應出Hello World!然後yield from語法可以讓我們方便的調用另一個生成器。由於asyncio.sleep也是一個coroutine,所以線程不會等待asyncio.sleep,而是直接中斷並執行一個消息循環。當asyncio.sleep返回時,線程就可以從yield from拿到返回值(此處是None),然後接著執行下一行語句。
把asyncio.sleep看成是一個好使1秒的IO操作,在此期間主線程並未等待,而是去執行EventLoop中其他可以執行的coroutine了,因此可以實現併發執行。
我們可以用Task封裝兩個coroutine試試:
<code>import threading
import asyncio
@asyncio.coroutine
def hello():
print('Hello World!(%s)' % threading.currentThread())
yield from asyncio.sleep(1)
print('Hello again! (%s)' % threading.currentThread())
loop = asyncio.get_event_loop()
tasks = [hello(), hello()]
loop.run_until_complete(asyncio.wait(tasks))
loop.close()/<code>
由打印的當前線程名稱可以看出,兩個coroutine是由同一個線程併發執行的。
為了簡化並更好的標識異步IO,從python 3.5開始引入了新的語法async和await,可以讓coroutine的代碼更簡潔易讀。
請注意,async和await是針對coroutine的新語法,要使用新的語法,只需要做兩部簡單的替換:
把@asyncio.coroutine替換為async 把yield from替換為await
例如:
<code>import asyncio
async def test(i):
print('test_1', i)
await asyncio.sleep(1)
print('test_2', i)
loop = asyncio.get_event_loop()
tasks = [test(i) for i in range(5)]
loop.run_until_complete(asyncio.wait(tasks))
loop.close()/<code>
閱讀更多 Python集結號 的文章