中文字幕av专区_日韩电影在线播放_精品国产精品久久一区免费式_av在线免费观看网站

溫馨提示×

溫馨提示×

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

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

Python如何協程

發布時間:2020-09-21 09:23:38 來源:億速云 閱讀:108 作者:Leah 欄目:編程語言

Python如何協程?很多新手對此不是很清楚,為了幫助大家解決這個難題,下面小編將為大家詳細講解,有這方面需求的人可以來學習下,希望你能有所收獲。

通常在Python中我們進行并發編程一般都是使用多線程或者多進程來實現的,對于計算型任務由于GIL的存在我們通常使用多進程來實現,而對于IO型任務我們可以通過線程調度來讓線程在執行IO任務時讓出GIL,從而實現表面上的并發。其實對于IO型任務我們還有一種選擇就是協程,協程是運行在單線程當中的"并發",協程相比多線程一大優勢就是省去了多線程之間的切換開銷,獲得了更大的運行效率。

協程,又稱微線程,纖程,英文名Coroutine。協程的作用是在執行函數A時可以隨時中斷去執行函數B,然后中斷函數B繼續執行函數A(可以自由切換)。但這一過程并不是函數調用,這一整個過程看似像多線程,然而協程只有一個線程執行。

那協程有什么優勢呢?

執行效率極高,因為子程序切換(函數)不是線程切換,由程序自身控制,沒有切換線程的開銷。所以與多線程相比,線程的數量越多,協程性能的優勢越明顯。

不需要多線程的鎖機制,因為只有一個線程,也不存在同時寫變量沖突,在控制共享資源時也不需要加鎖,因此執行效率高很多。

協程可以處理IO密集型程序的效率問題,但是處理CPU密集型不是它的長處,如要充分發揮CPU利用率可以結合多進程+協程。

Python中的協程經歷了很長的一段發展歷程。其大概經歷了如下三個階段:

最初的生成器變形yield/send

引入@asyncio.coroutine和yield from

引入async/await關鍵字

上述是協程概念和優勢的一些簡介,感覺會比較抽象,Python2.x對協程的支持比較有限,生成器yield實現了一部分但不完全,gevent模塊倒是有比較好的實現;Python3.4加入了asyncio模塊,在Python3.5中又提供了async/await語法層面的支持,Python3.6中asyncio模塊更加完善和穩定。接下來我們圍繞這些內容詳細闡述一下。

Python2.x協程

python2.x實現協程的方式有:

yield + send

gevent (見后續章節)

yield + send(利用生成器實現協程)

我們通過“生產者-消費者”模型來看一下協程的應用,生產者生產消息后,直接通過yield跳轉到消費者開始執行,待消費者執行完畢后,切換回生產者繼續生產。

#-*- coding:utf8 -*-
def consumer():
    r = ''
    while True:
    n = yield r
    if not n:
        return
    print('[CONSUMER]Consuming %s...' % n)
    r = '200 OK'
def producer(c):
    # 啟動生成器
    c.send(None)
    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()
    producer(c)

復制代碼send(msg)與next()的區別在于send可以傳遞參數給yield表達式,這時傳遞的參數會作為yield表達式的值,而yield的參數是返回給調用者的值。換句話說,就是send可以強行修改上一個yield表達式的值。比如函數中有一個yield賦值a = yield 5,第一次迭代到這里會返回5,a還沒有賦值。第二次迭代時,使用send(10),那么就是強行修改yield 5表達式的值為10,本來是5的,結果a = 10。send(msg)與next()都有返回值,它們的返回值是當前迭代遇到yield時,yield后面表達式的值,其實就是當前迭代中yield后面的參數。第一次調用send時必須是send(None),否則會報錯,之所以為None是因為這時候還沒有一個yield表達式可以用來賦值。上述例子運行之后輸出結果如下:

[PRODUCER]Producing 1...
[CONSUMER]Consuming 1...
[PRODUCER]Consumer return: 200 OK
[PRODUCER]Producing 2...
[CONSUMER]Consuming 2...
[PRODUCER]Consumer return: 200 OK
[PRODUCER]Producing 3...
[CONSUMER]Consuming 3...
[PRODUCER]Consumer return: 200 OK
[PRODUCER]Producing 4...
[CONSUMER]Consuming 4...
[PRODUCER]Consumer return: 200 OK
[PRODUCER]Producing 5...
[CONSUMER]Consuming 5...
[PRODUCER]Consumer return: 200 OK

Python3.x協程

除了Python2.x中協程的實現方式,Python3.x還提供了如下方式實現協程:

asyncio + yield from (python3.4+)

asyncio + async/await (python3.5+)

Python3.4以后引入了asyncio模塊,可以很好的支持協程。

asyncio + yield from

asyncio是Python3.4版本引入的標準庫,直接內置了對異步IO的支持。asyncio的異步操作,需要在coroutine中通過yield from完成。看如下代碼(需要在Python3.4以后版本使用):

#-*- coding:utf8 -*-
import asyncio
@asyncio.coroutine
def test(i):
    print('test_1', i)
    r = yield from asyncio.sleep(1)
    print('test_2', i)
if __name__ == '__main__':
    loop = asyncio.get_event_loop()
    tasks = [test(i) for i in range(3)]
    loop.run_until_complete(asyncio.wait(tasks))
    loop.close()

@asyncio.coroutine把一個generator標記為coroutine類型,然后就把這個coroutine扔到EventLoop中執行。test()會首先打印出test_1,然后yield from語法可以讓我們方便地調用另一個generator。由于asyncio.sleep()也是一個coroutine,所以線程不會等待asyncio.sleep(),而是直接中斷并執行下一個消息循環。當asyncio.sleep()返回時,線程就可以從yield from拿到返回值(此處是None),然后接著執行下一行語句。把asyncio.sleep(1)看成是一個耗時1秒的IO操作,在此期間主線程并未等待,而是去執行EventLoop中其他可以執行的coroutine了,因此可以實現并發執行。

asyncio + async/await

為了簡化并更好地標識異步IO,從Python3.5開始引入了新的語法async和await,可以讓coroutine的代碼更簡潔易讀。請注意,async和await是coroutine的新語法,使用新語法只需要做兩步簡單的替換:

把@asyncio.coroutine替換為async

把yield from替換為await

看如下代碼(在Python3.5以上版本使用):

#-*- coding:utf8 -*-
import asyncio
async def test(i):
    print('test_1', i)
    await asyncio.sleep(1)
    print('test_2', i)
    
if __name__ == '__main__':
    loop = asyncio.get_event_loop()
    tasks = [test(i) for i in range(3)]
    loop.run_until_complete(asyncio.wait(tasks))
    loop.close()

運行結果與之前一致。與前一節相比,這里只是把yield from換成了await,@asyncio.coroutine換成了async,其余不變。

Gevent

Gevent是一個基于Greenlet實現的網絡庫,通過greenlet實現協程。基本思想是一個greenlet就認為是一個協程,當一個greenlet遇到IO操作的時候,比如訪問網絡,就會自動切換到其他的greenlet,等到IO操作完成,再在適當的時候切換回來繼續執行。由于IO操作非常耗時,經常使程序處于等待狀態,有了gevent為我們自動切換協程,就保證總有greenlet在運行,而不是等待IO操作。

Greenlet是作為一個C擴展模塊,它封裝了libevent事件循環的API,可以讓開發者在不改變編程習慣的同時,用同步的方式寫異步IO的代碼。

#-*- coding:utf8 -*-
import gevent
def test(n):
    for i in range(n):
        print(gevent.getcurrent(), i)
if __name__ == '__main__':
    g1 = gevent.spawn(test, 3)
    g2 = gevent.spawn(test, 3)
    g3 = gevent.spawn(test, 3)
    
    g1.join()
    g2.join()
    g3.join()

運行結果:

<Greenlet at 0x10a6eea60: test(3)> 0
<Greenlet at 0x10a6eea60: test(3)> 1
<Greenlet at 0x10a6eea60: test(3)> 2
<Greenlet at 0x10a6eed58: test(3)> 0
<Greenlet at 0x10a6eed58: test(3)> 1
<Greenlet at 0x10a6eed58: test(3)> 2
<Greenlet at 0x10a6eedf0: test(3)> 0
<Greenlet at 0x10a6eedf0: test(3)> 1
<Greenlet at 0x10a6eedf0: test(3)> 2

復制代碼可以看到3個greenlet是依次運行而不是交替運行。要讓greenlet交替運行,可以通過gevent.sleep()交出控制權:

def test(n):
    for i in range(n):
        print(gevent.getcurrent(), i)
        gevent.sleep(1)

運行結果:

<Greenlet at 0x10382da60: test(3)> 0
<Greenlet at 0x10382dd58: test(3)> 0
<Greenlet at 0x10382ddf0: test(3)> 0
<Greenlet at 0x10382da60: test(3)> 1
<Greenlet at 0x10382dd58: test(3)> 1
<Greenlet at 0x10382ddf0: test(3)> 1
<Greenlet at 0x10382da60: test(3)> 2
<Greenlet at 0x10382dd58: test(3)> 2
<Greenlet at 0x10382ddf0: test(3)> 2

當然在實際的代碼里,我們不會用gevent.sleep()去切換協程,而是在執行到IO操作時gevent會自動完成,所以gevent需要將Python自帶的一些標準庫的運行方式由阻塞式調用變為協作式運行。這一過程在啟動時通過monkey patch完成:

#-*- coding:utf8 -*-
from gevent import monkey; monkey.patch_all()
from urllib import request
import gevent
def test(url):
    print('Get: %s' % url)
    response = request.urlopen(url)
    content = response.read().decode('utf8')
    print('%d bytes received from %s.' % (len(content), url))
    
if __name__ == '__main__':
    gevent.joinall([
    gevent.spawn(test, 'http://httpbin.org/ip'),
    gevent.spawn(test, 'http://httpbin.org/uuid'),
    gevent.spawn(test, 'http://httpbin.org/user-agent')
    ])

運行結果:

Get: http://httpbin.org/ip
Get: http://httpbin.org/uuid
Get: http://httpbin.org/user-agent
53 bytes received from http://httpbin.org/uuid.
40 bytes received from http://httpbin.org/user-agent.
31 bytes received from http://httpbin.org/ip.

從結果看,3個網絡操作是并發執行的,而且結束順序不同,但只有一個線程。

總結

至此Python中的協程就介紹完畢了,示例程序中都是以sleep代表異步IO的,在實際項目中可以使用協程異步的讀寫網絡、讀寫文件、渲染界面等,而在等待協程完成的同時,CPU還可以進行其他的計算,協程的作用正在于此。那么協程和多線程的差異在哪里呢?多線程的切換需要靠操作系統來完成,當線程越來越多時切換的成本會很高,而協程是在一個線程內切換的,切換過程由我們自己控制,因此開銷小很多,這就是協程和多線程的根本差異。

看完上述內容是否對您有幫助呢?如果還想對相關知識有進一步的了解或閱讀更多相關文章,請關注億速云行業資訊頻道,感謝您對億速云的支持。

向AI問一下細節

免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

AI

库尔勒市| 沁源县| 来宾市| 哈巴河县| 本溪| 汕头市| 丰镇市| 易门县| 云浮市| 夏津县| 喀喇| 金山区| 岚皋县| 广昌县| 贵德县| 名山县| 武山县| 林甸县| 潍坊市| 阳山县| 合川市| 左权县| 清水河县| 调兵山市| 修水县| 曲阳县| 中西区| 宾川县| 安陆市| 无极县| 香格里拉县| 金山区| 文山县| 九江市| 毕节市| 慈利县| 昌乐县| 西乡县| 宿迁市| 凉山| 明溪县|