協程在python中如何使用


協程在python中如何使用

什麼是協程

協程,又稱為微線程,纖程,英文名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>


分享到:


相關文章: