Python如何協(xié)程?很多新手對(duì)此不是很清楚,為了幫助大家解決這個(gè)難題,下面小編將為大家詳細(xì)講解,有這方面需求的人可以來(lái)學(xué)習(xí)下,希望你能有所收獲。
成都創(chuàng)新互聯(lián)公司主營(yíng)垣曲網(wǎng)站建設(shè)的網(wǎng)絡(luò)公司,主營(yíng)網(wǎng)站建設(shè)方案,成都app軟件開(kāi)發(fā),垣曲h5微信小程序搭建,垣曲網(wǎng)站營(yíng)銷(xiāo)推廣歡迎垣曲等地區(qū)企業(yè)咨詢
通常在Python中我們進(jìn)行并發(fā)編程一般都是使用多線程或者多進(jìn)程來(lái)實(shí)現(xiàn)的,對(duì)于計(jì)算型任務(wù)由于GIL的存在我們通常使用多進(jìn)程來(lái)實(shí)現(xiàn),而對(duì)于IO型任務(wù)我們可以通過(guò)線程調(diào)度來(lái)讓線程在執(zhí)行IO任務(wù)時(shí)讓出GIL,從而實(shí)現(xiàn)表面上的并發(fā)。其實(shí)對(duì)于IO型任務(wù)我們還有一種選擇就是協(xié)程,協(xié)程是運(yùn)行在單線程當(dāng)中的"并發(fā)",協(xié)程相比多線程一大優(yōu)勢(shì)就是省去了多線程之間的切換開(kāi)銷(xiāo),獲得了更大的運(yùn)行效率。
協(xié)程,又稱微線程,纖程,英文名Coroutine。協(xié)程的作用是在執(zhí)行函數(shù)A時(shí)可以隨時(shí)中斷去執(zhí)行函數(shù)B,然后中斷函數(shù)B繼續(xù)執(zhí)行函數(shù)A(可以自由切換)。但這一過(guò)程并不是函數(shù)調(diào)用,這一整個(gè)過(guò)程看似像多線程,然而協(xié)程只有一個(gè)線程執(zhí)行。
那協(xié)程有什么優(yōu)勢(shì)呢?
執(zhí)行效率極高,因?yàn)樽映绦蚯袚Q(函數(shù))不是線程切換,由程序自身控制,沒(méi)有切換線程的開(kāi)銷(xiāo)。所以與多線程相比,線程的數(shù)量越多,協(xié)程性能的優(yōu)勢(shì)越明顯。
不需要多線程的鎖機(jī)制,因?yàn)橹挥幸粋€(gè)線程,也不存在同時(shí)寫(xiě)變量沖突,在控制共享資源時(shí)也不需要加鎖,因此執(zhí)行效率高很多。
協(xié)程可以處理IO密集型程序的效率問(wèn)題,但是處理CPU密集型不是它的長(zhǎng)處,如要充分發(fā)揮CPU利用率可以結(jié)合多進(jìn)程+協(xié)程。
Python中的協(xié)程經(jīng)歷了很長(zhǎng)的一段發(fā)展歷程。其大概經(jīng)歷了如下三個(gè)階段:
最初的生成器變形yield/send
引入@asyncio.coroutine和yield from
引入async/await關(guān)鍵字
上述是協(xié)程概念和優(yōu)勢(shì)的一些簡(jiǎn)介,感覺(jué)會(huì)比較抽象,Python2.x對(duì)協(xié)程的支持比較有限,生成器yield實(shí)現(xiàn)了一部分但不完全,gevent模塊倒是有比較好的實(shí)現(xiàn);Python3.4加入了asyncio模塊,在Python3.5中又提供了async/await語(yǔ)法層面的支持,Python3.6中asyncio模塊更加完善和穩(wěn)定。接下來(lái)我們圍繞這些內(nèi)容詳細(xì)闡述一下。
Python2.x協(xié)程
python2.x實(shí)現(xiàn)協(xié)程的方式有:
yield + send
gevent (見(jiàn)后續(xù)章節(jié))
yield + send(利用生成器實(shí)現(xiàn)協(xié)程)
我們通過(guò)“生產(chǎn)者-消費(fèi)者”模型來(lái)看一下協(xié)程的應(yīng)用,生產(chǎn)者生產(chǎn)消息后,直接通過(guò)yield跳轉(zhuǎn)到消費(fèi)者開(kāi)始執(zhí)行,待消費(fèi)者執(zhí)行完畢后,切換回生產(chǎn)者繼續(xù)生產(chǎn)。
#-*- 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): # 啟動(dòng)生成器 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)
復(fù)制代碼send(msg)與next()的區(qū)別在于send可以傳遞參數(shù)給yield表達(dá)式,這時(shí)傳遞的參數(shù)會(huì)作為yield表達(dá)式的值,而yield的參數(shù)是返回給調(diào)用者的值。換句話說(shuō),就是send可以強(qiáng)行修改上一個(gè)yield表達(dá)式的值。比如函數(shù)中有一個(gè)yield賦值a = yield 5,第一次迭代到這里會(huì)返回5,a還沒(méi)有賦值。第二次迭代時(shí),使用send(10),那么就是強(qiáng)行修改yield 5表達(dá)式的值為10,本來(lái)是5的,結(jié)果a = 10。send(msg)與next()都有返回值,它們的返回值是當(dāng)前迭代遇到y(tǒng)ield時(shí),yield后面表達(dá)式的值,其實(shí)就是當(dāng)前迭代中yield后面的參數(shù)。第一次調(diào)用send時(shí)必須是send(None),否則會(huì)報(bào)錯(cuò),之所以為None是因?yàn)檫@時(shí)候還沒(méi)有一個(gè)yield表達(dá)式可以用來(lái)賦值。上述例子運(yùn)行之后輸出結(jié)果如下:
[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協(xié)程
除了Python2.x中協(xié)程的實(shí)現(xiàn)方式,Python3.x還提供了如下方式實(shí)現(xiàn)協(xié)程:
asyncio + yield from (python3.4+)
asyncio + async/await (python3.5+)
Python3.4以后引入了asyncio模塊,可以很好的支持協(xié)程。
asyncio + yield from
asyncio是Python3.4版本引入的標(biāo)準(zhǔn)庫(kù),直接內(nèi)置了對(duì)異步IO的支持。asyncio的異步操作,需要在coroutine中通過(guò)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把一個(gè)generator標(biāo)記為coroutine類(lèi)型,然后就把這個(gè)coroutine扔到EventLoop中執(zhí)行。test()會(huì)首先打印出test_1,然后yield from語(yǔ)法可以讓我們方便地調(diào)用另一個(gè)generator。由于asyncio.sleep()也是一個(gè)coroutine,所以線程不會(huì)等待asyncio.sleep(),而是直接中斷并執(zhí)行下一個(gè)消息循環(huán)。當(dāng)asyncio.sleep()返回時(shí),線程就可以從yield from拿到返回值(此處是None),然后接著執(zhí)行下一行語(yǔ)句。把a(bǔ)syncio.sleep(1)看成是一個(gè)耗時(shí)1秒的IO操作,在此期間主線程并未等待,而是去執(zhí)行EventLoop中其他可以執(zhí)行的coroutine了,因此可以實(shí)現(xiàn)并發(fā)執(zhí)行。
asyncio + async/await
為了簡(jiǎn)化并更好地標(biāo)識(shí)異步IO,從Python3.5開(kāi)始引入了新的語(yǔ)法async和await,可以讓coroutine的代碼更簡(jiǎn)潔易讀。請(qǐng)注意,async和await是coroutine的新語(yǔ)法,使用新語(yǔ)法只需要做兩步簡(jiǎn)單的替換:
把@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()
運(yùn)行結(jié)果與之前一致。與前一節(jié)相比,這里只是把yield from換成了await,@asyncio.coroutine換成了async,其余不變。
Gevent
Gevent是一個(gè)基于Greenlet實(shí)現(xiàn)的網(wǎng)絡(luò)庫(kù),通過(guò)greenlet實(shí)現(xiàn)協(xié)程。基本思想是一個(gè)greenlet就認(rèn)為是一個(gè)協(xié)程,當(dāng)一個(gè)greenlet遇到IO操作的時(shí)候,比如訪問(wèn)網(wǎng)絡(luò),就會(huì)自動(dòng)切換到其他的greenlet,等到IO操作完成,再在適當(dāng)?shù)臅r(shí)候切換回來(lái)繼續(xù)執(zhí)行。由于IO操作非常耗時(shí),經(jīng)常使程序處于等待狀態(tài),有了gevent為我們自動(dòng)切換協(xié)程,就保證總有g(shù)reenlet在運(yùn)行,而不是等待IO操作。
Greenlet是作為一個(gè)C擴(kuò)展模塊,它封裝了libevent事件循環(huán)的API,可以讓開(kāi)發(fā)者在不改變編程習(xí)慣的同時(shí),用同步的方式寫(xiě)異步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()
運(yùn)行結(jié)果:
<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
復(fù)制代碼可以看到3個(gè)greenlet是依次運(yùn)行而不是交替運(yùn)行。要讓greenlet交替運(yùn)行,可以通過(guò)gevent.sleep()交出控制權(quán):
def test(n): for i in range(n): print(gevent.getcurrent(), i) gevent.sleep(1)
運(yùn)行結(jié)果:
<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
當(dāng)然在實(shí)際的代碼里,我們不會(huì)用gevent.sleep()去切換協(xié)程,而是在執(zhí)行到IO操作時(shí)gevent會(huì)自動(dòng)完成,所以gevent需要將Python自帶的一些標(biāo)準(zhǔn)庫(kù)的運(yùn)行方式由阻塞式調(diào)用變?yōu)閰f(xié)作式運(yùn)行。這一過(guò)程在啟動(dòng)時(shí)通過(guò)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') ])
運(yùn)行結(jié)果:
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.
從結(jié)果看,3個(gè)網(wǎng)絡(luò)操作是并發(fā)執(zhí)行的,而且結(jié)束順序不同,但只有一個(gè)線程。
總結(jié)
至此Python中的協(xié)程就介紹完畢了,示例程序中都是以sleep代表異步IO的,在實(shí)際項(xiàng)目中可以使用協(xié)程異步的讀寫(xiě)網(wǎng)絡(luò)、讀寫(xiě)文件、渲染界面等,而在等待協(xié)程完成的同時(shí),CPU還可以進(jìn)行其他的計(jì)算,協(xié)程的作用正在于此。那么協(xié)程和多線程的差異在哪里呢?多線程的切換需要靠操作系統(tǒng)來(lái)完成,當(dāng)線程越來(lái)越多時(shí)切換的成本會(huì)很高,而協(xié)程是在一個(gè)線程內(nèi)切換的,切換過(guò)程由我們自己控制,因此開(kāi)銷(xiāo)小很多,這就是協(xié)程和多線程的根本差異。
看完上述內(nèi)容是否對(duì)您有幫助呢?如果還想對(duì)相關(guān)知識(shí)有進(jìn)一步的了解或閱讀更多相關(guān)文章,請(qǐng)關(guān)注創(chuàng)新互聯(lián)行業(yè)資訊頻道,感謝您對(duì)創(chuàng)新互聯(lián)的支持。
當(dāng)前文章:Python如何協(xié)程
鏈接分享:http://www.chinadenli.net/article30/gepcpo.html
成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供網(wǎng)站收錄、、網(wǎng)站維護(hù)、動(dòng)態(tài)網(wǎng)站、品牌網(wǎng)站設(shè)計(jì)、建站公司
聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請(qǐng)盡快告知,我們將會(huì)在第一時(shí)間刪除。文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如需處理請(qǐng)聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時(shí)需注明來(lái)源: 創(chuàng)新互聯(lián)