溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點(diǎn)擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

Python與協(xié)程從Python2—Python3

發(fā)布時(shí)間:2020-06-30 16:40:29 來源:網(wǎng)絡(luò) 閱讀:306 作者:嘉為科技 欄目:編程語言

Python與協(xié)程從Python2—Python3

協(xié)程,又稱微線程、纖程,英文名Coroutine;用一句話說明什么是線程的話:協(xié)程是一種用戶態(tài)的輕量級線程。

Python對于協(xié)程的支持在python2中還比較簡單,但是也有可以使用的第三方庫,在python3中開始全面支持,也成為python3的一個(gè)核心功能,很值得學(xué)習(xí)。

協(xié)程介紹

協(xié)程,又稱微線程、纖程,英文名Coroutine;用一句話說明什么是線程的話:協(xié)程是一種用戶態(tài)的輕量級線程。

協(xié)程擁有自己的寄存器上下文和棧。協(xié)程調(diào)度切換時(shí),將寄存器上下文和棧保存到其他地方,在切回來的時(shí)候,恢復(fù)先前保存的寄存器上下文和棧。因此:協(xié)程能保留上一次調(diào)用時(shí)的狀態(tài)(即所有局部狀態(tài)的一個(gè)特定組合),每次過程重入時(shí),就相當(dāng)于進(jìn)入上一次調(diào)用的狀態(tài),換種說法:進(jìn)入上一次離開時(shí)所處邏輯流的位置。

協(xié)程的優(yōu)點(diǎn):

1)無需線程上下文切換的開銷

2)無需原子操作鎖定及同步的開銷

3)方便切換控制流,簡化編程模型

4)高并發(fā)+高擴(kuò)展性+低成本:一個(gè)CPU支持上萬的協(xié)程都不是問題。所以很適合用于高并發(fā)處理。

協(xié)程的缺點(diǎn):

1)無法利用多核資源:協(xié)程的本質(zhì)是個(gè)單線程,它不能同時(shí)將 單個(gè)CPU 的多個(gè)核用上,協(xié)程需要和進(jìn)程配合才能運(yùn)行在多CPU上

2)進(jìn)行阻塞(Blocking)操作(如IO時(shí))會(huì)阻塞掉整個(gè)程序

Python2中的協(xié)程

yield關(guān)鍵字
Python2對于協(xié)程的支持,是通過yield關(guān)鍵字實(shí)現(xiàn)的,下面示例代碼是一個(gè)常見的生產(chǎn)者—消費(fèi)者模型,代碼示例如下:

def consumer():

    r = ''

    while True:

        n = yield r

        if not n:

            continue

        print('[CONSUMER] Consuming %s...' % n)

        r = '200 OK'

def produce(c):

    c.next()

    n = 0

    while n < 5:

        n = n + 1

        print('[PRODUCER] Producing %s...' % n)

        r = c.send(n)

        print('[PRODUCER] Consumer return: %s' % r)

    c.close()

if __name__ == '__main__':

    c = consumer()

    produce(c)

執(zhí)行結(jié)果:

Python與協(xié)程從Python2—Python3

注意到consumer函數(shù)是一個(gè)generator(生成器),把一個(gè)consumer傳入produce后:

1)首先調(diào)用c.next()啟動(dòng)生成器;

2)然后,一旦生產(chǎn)了東西,通過c.send(n)切換到consumer執(zhí)行;

3)consumer通過yield拿到消息,處理,又通過yield把結(jié)果傳回;

4)produce拿到consumer處理的結(jié)果,繼續(xù)生產(chǎn)下一條消息;

5)produce決定不生產(chǎn)了,通過c.close()關(guān)閉consumer,整個(gè)過程結(jié)束。

整個(gè)流程無鎖,由一個(gè)線程執(zhí)行,produce和consumer協(xié)作完成任務(wù),所以稱為“協(xié)程”,而非線程的搶占式多任務(wù)。

傳統(tǒng)的生產(chǎn)者-消費(fèi)者模型是一個(gè)線程寫消息,一個(gè)線程取消息,通過鎖機(jī)制控制隊(duì)列和等待,但一不小心就可能死鎖。

如果改用協(xié)程,生產(chǎn)者生產(chǎn)消息后,直接通過yield跳轉(zhuǎn)到消費(fèi)者開始執(zhí)行,待消費(fèi)者執(zhí)行完畢后,切換回生產(chǎn)者繼續(xù)生產(chǎn),效率極高。

Python對協(xié)程的支持還非常有限,用在generator中的yield可以一定程度上實(shí)現(xiàn)協(xié)程。雖然支持不完全,但已經(jīng)可以發(fā)揮相當(dāng)大的威力了。

gevent模塊
Python通過yield提供了對協(xié)程的基本支持,但是不完全。而第三方的gevent為Python提供了比較完善的協(xié)程支持。gevent是第三方庫,通過greenlet實(shí)現(xiàn)協(xié)程,其基本思想是:

當(dāng)一個(gè)greenlet遇到IO操作時(shí),比如訪問網(wǎng)絡(luò),就自動(dòng)切換到其他的greenlet,等到IO操作完成,再在適當(dāng)?shù)臅r(shí)候切換回來繼續(xù)執(zhí)行。由于IO操作非常耗時(shí),經(jīng)常使程序處于等待狀態(tài),有了gevent為我們自動(dòng)切換協(xié)程,就保證總有g(shù)reenlet在運(yùn)行,而不是等待IO。由于切換是在IO操作時(shí)自動(dòng)完成,所以gevent需要修改Python自帶的一些標(biāo)準(zhǔn)庫,這一過程在啟動(dòng)時(shí)通過monkey patch完成。

示例代碼如下:


from gevent import monkey; monkey.patch_all()

import gevent

import urllib2

def f(url):

    print('GET: %s' % url)

    resp = urllib2.urlopen(url)

    data = resp.read()

    print('%d bytes received from %s.' % (len(data), url))

gevent.joinall([

        gevent.spawn(f, 'https://www.python.org/'),

        gevent.spawn(f, 'https://www.yahoo.com/'),

        gevent.spawn(f, 'https://github.com/'),

])

執(zhí)行結(jié)果:
Python與協(xié)程從Python2—Python3

從執(zhí)行結(jié)果可以看到,網(wǎng)站訪問的順序是自動(dòng)切換的。

gevent優(yōu)缺
使用gevent,可以獲得極高的并發(fā)性能,但gevent只能在Unix/Linux下運(yùn)行,在Windows下不保證正常安裝和運(yùn)行。Python創(chuàng)始人Gvanrossum從來不喜歡Gevent,而是更愿意另辟蹊徑的實(shí)現(xiàn)asyncio(python3中的異步實(shí)現(xiàn))。

1)Monkey-patching。中文「猴子補(bǔ)丁」,常用于對測試環(huán)境做一些hack。Gvanrossum說用它就是”patch-and-pray”,由于Gevent直接修改標(biāo)準(zhǔn)庫里面大部分的阻塞式系統(tǒng)調(diào)用,包括socket、ssl、threading和 select等模塊,而變?yōu)閰f(xié)作式運(yùn)行。但是無法保證在復(fù)雜的生產(chǎn)環(huán)境中有哪些地方使用這些標(biāo)準(zhǔn)庫會(huì)由于打了補(bǔ)丁而出現(xiàn)奇怪的問題,那么只能祈禱(pray)了。

2)其次,在Python之禪中明確說過:「Explicit is better than implicit.」,猴子補(bǔ)丁明顯的背離了這個(gè)原則。

3)第三方庫支持。得確保項(xiàng)目中用到其他用到的網(wǎng)絡(luò)庫也必須使用純Python或者明確說明支持Gevent,而且就算有這樣的第三方庫,也需要擔(dān)心這個(gè)第三方庫的代碼質(zhì)量和功能性。

4)Greenlet不支持Jython和IronPython,這樣就無法把gevent設(shè)計(jì)成一個(gè)標(biāo)準(zhǔn)庫了。

之前是沒有選擇,很多人選擇了Gevent,而現(xiàn)在明確的有了更正統(tǒng)的、正確的選擇:asyncio(下一節(jié)會(huì)介紹)。所以建議大家了解Gevent,擁抱asyncio。

另外,如果知道現(xiàn)在以及未來使用Gevent不會(huì)給項(xiàng)目造成困擾,那么用Gevent也是可以的。

Python3中的協(xié)程

Gvanrossum希望在Python 3 實(shí)現(xiàn)一個(gè)原生的基于生成器的協(xié)程庫,其中直接內(nèi)置了對異步IO的支持,這就是asyncio,它在Python 3.4被引入到標(biāo)準(zhǔn)庫。

下面將簡單介紹asyncio的使用:

1)event_loop 事件循環(huán):程序開啟一個(gè)無限的循環(huán),程序員會(huì)把一些函數(shù)注冊到事件循環(huán)上。當(dāng)滿足事件發(fā)生的時(shí)候,調(diào)用相應(yīng)的協(xié)程函數(shù)。

2)coroutine 協(xié)程:協(xié)程對象,指一個(gè)使用async關(guān)鍵字定義的函數(shù),它的調(diào)用不會(huì)立即執(zhí)行函數(shù),而是會(huì)返回一個(gè)協(xié)程對象。協(xié)程對象需要注冊到事件循環(huán),由事件循環(huán)調(diào)用。

3)task 任務(wù):一個(gè)協(xié)程對象就是一個(gè)原生可以掛起的函數(shù),任務(wù)則是對協(xié)程進(jìn)一步封裝,其中包含任務(wù)的各種狀態(tài)。

4)future: 代表將來執(zhí)行或沒有執(zhí)行的任務(wù)的結(jié)果。它和task上沒有本質(zhì)的區(qū)別

5)async/await 關(guān)鍵字:python3.5 用于定義協(xié)程的關(guān)鍵字,async定義一個(gè)協(xié)程,await用于掛起阻塞的異步調(diào)用接口。

代碼示例如下:

import asyncio

import time

now = lambda: time.time()

async def do_some_work(x):

    print('Waiting: {}s'.format(x))

    await asyncio.sleep(x)

    return 'Done after {}s'.format(x)

async def main():

    coroutine1 = do_some_work(1)

    coroutine2 = do_some_work(5)

    coroutine3 = do_some_work(3)

    tasks = [

        asyncio.ensure_future(coroutine1),

        asyncio.ensure_future(coroutine2),

        asyncio.ensure_future(coroutine3)

    ]

    done, pending = await asyncio.wait(tasks)

    for task in done:

        print('Task ret: ', task.result())

start = now()

loop = asyncio.get_event_loop()

task = asyncio.ensure_future(main())

try:

    loop.run_until_complete(task)

    print('TIME: ', now() - start)

except KeyboardInterrupt as e:

    print(asyncio.Task.all_tasks())

    print(asyncio.gather(*asyncio.Task.all_tasks()).cancel())

    loop.stop()

    loop.run_forever()

finally:

    loop.close()

執(zhí)行結(jié)果:
Python與協(xié)程從Python2—Python3

可以看到程序執(zhí)行時(shí)間是以等待時(shí)間最長的為準(zhǔn)。

使用async可以定義協(xié)程對象,使用await可以針對耗時(shí)的操作進(jìn)行掛起,就像生成器里的yield一樣,函數(shù)讓出控制權(quán)。協(xié)程遇到await,事件循環(huán)將會(huì)掛起該協(xié)程,執(zhí)行別的協(xié)程,直到其他的協(xié)程也掛起或者執(zhí)行完畢,再進(jìn)行下一個(gè)協(xié)程的執(zhí)行。耗時(shí)的操作一般是一些IO操作,例如網(wǎng)絡(luò)請求,文件讀取等。我們使用asyncio.sleep函數(shù)來模擬IO操作。協(xié)程的目的也是讓這些IO操作異步化。

Asyncio是python3中一個(gè)強(qiáng)大的內(nèi)置庫,上述只是簡單的介紹了asyncio的用法有興趣的話,很值得去學(xué)習(xí)一下!

向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI