這篇文章主要介紹“如何理解Python中的協(xié)程”,在日常操作中,相信很多人在如何理解Python中的協(xié)程問(wèn)題上存在疑惑,小編查閱了各式資料,整理出簡(jiǎn)單好用的操作方法,希望對(duì)大家解答”如何理解Python中的協(xié)程”的疑惑有所幫助!接下來(lái),請(qǐng)跟著小編一起來(lái)學(xué)習(xí)吧!
創(chuàng)新互聯(lián)堅(jiān)持“要么做到,要么別承諾”的工作理念,服務(wù)領(lǐng)域包括:網(wǎng)站設(shè)計(jì)、網(wǎng)站制作、企業(yè)官網(wǎng)、英文網(wǎng)站、手機(jī)端網(wǎng)站、網(wǎng)站推廣等服務(wù),滿足客戶于互聯(lián)網(wǎng)時(shí)代的雙牌網(wǎng)站設(shè)計(jì)、移動(dòng)媒體設(shè)計(jì)的需求,幫助企業(yè)找到有效的互聯(lián)網(wǎng)解決方案。努力成為您成熟可靠的網(wǎng)絡(luò)建設(shè)合作伙伴!
我們?cè)?jīng)在golang關(guān)于goroutine的文章當(dāng)中簡(jiǎn)單介紹過(guò)協(xié)程的概念,我們?cè)賮?lái)簡(jiǎn)單review一下。協(xié)程又稱為是微線程,英文名是Coroutine。它和線程一樣可以調(diào)度,但是不同的是線程的啟動(dòng)和調(diào)度需要通過(guò)操作系統(tǒng)來(lái)處理。并且線程的啟動(dòng)和銷毀需要涉及一些操作系統(tǒng)的變量申請(qǐng)和銷毀處理,需要的時(shí)間比較長(zhǎng)。而協(xié)程呢,它的調(diào)度和銷毀都是程序自己來(lái)控制的,因此它更加輕量級(jí)也更加靈活。
協(xié)程有這么多優(yōu)點(diǎn),自然也會(huì)有一些缺點(diǎn),其中最大的缺點(diǎn)就是需要編程語(yǔ)言自己支持,否則的話需要開發(fā)者自己通過(guò)一些方法來(lái)實(shí)現(xiàn)協(xié)程。對(duì)于大部分語(yǔ)言來(lái)說(shuō),都不支持這一機(jī)制。go語(yǔ)言由于天然支持協(xié)程,并且支持得非常好,使得它廣受好評(píng),短短幾年時(shí)間就迅速流行起來(lái)。
對(duì)于Python來(lái)說(shuō),本身就有著一個(gè)GIL這個(gè)巨大的先天問(wèn)題。GIL是Python的全局鎖,在它的限制下一個(gè)Python進(jìn)程同一時(shí)間只能同時(shí)執(zhí)行一個(gè)線程,即使是在多核心的機(jī)器當(dāng)中。這就大大影響了Python的性能,尤其是在CPU密集型的工作上。所以為了提升Python的性能,很多開發(fā)者想出了使用多進(jìn)程+協(xié)程的方式。一開始是開發(fā)者自行實(shí)現(xiàn)的,后來(lái)在Python3.4的版本當(dāng)中,官方也收入了這個(gè)功能,因此目前可以光明正大地說(shuō),Python是支持協(xié)程的語(yǔ)言了。
生成器(generator)
生成器我們也在之前的文章當(dāng)中介紹過(guò),為什么我們介紹協(xié)程需要用到生成器呢,是因?yàn)镻ython的協(xié)程底層就是通過(guò)生成器來(lái)實(shí)現(xiàn)的。
通過(guò)生成器來(lái)實(shí)現(xiàn)協(xié)程的原因也很簡(jiǎn)單,我們都知道協(xié)程需要切換掛起,而生成器當(dāng)中有一個(gè)yield關(guān)鍵字,剛好可以實(shí)現(xiàn)這個(gè)功能。所以當(dāng)初那些自己在Python當(dāng)中開發(fā)協(xié)程功能的程序員都是通過(guò)生成器來(lái)實(shí)現(xiàn)的,我們想要理解Python當(dāng)中協(xié)程的運(yùn)用,就必須從最原始的生成器開始。
生成器我們很熟悉了,本質(zhì)上就是帶有yield這個(gè)關(guān)鍵詞的函數(shù)。
def test(): n = 0 while n < 10: val = yield n print('val = {}'.format(val)) n += 1
這個(gè)函數(shù)當(dāng)中如果沒(méi)有yield這個(gè)語(yǔ)句,那么它就是一個(gè)普通的Python函數(shù)。加上了val = yield n這個(gè)語(yǔ)句之后,它有什么變化呢?
我們嘗試著運(yùn)行一下:
# 調(diào)用test函數(shù)獲得一個(gè)生成器 g = test() print(next(g)) print(next(g)) print(next(g))
得到這么一個(gè)結(jié)果:
輸出的0,1,2很好理解,就是通過(guò)next(g)返回的,這個(gè)也是生成器的標(biāo)準(zhǔn)用法。奇怪的是為什么val=None呢?val不應(yīng)該等于n么?
這里想不明白是正常的,因?yàn)檫@里涉及到了一個(gè)新的用法就是生成器的send方法。當(dāng)我們?cè)趛ield語(yǔ)句之前加上變量名的時(shí)候,它的含義其實(shí)是返回yield之后的內(nèi)容,再?gòu)耐饨缃邮找粋€(gè)變量。也就是說(shuō)當(dāng)我們執(zhí)行next(g)的時(shí)候,會(huì)從獲取yield之后的數(shù),當(dāng)我們執(zhí)行g(shù).send()時(shí),傳入的值會(huì)被賦值給yield之前的數(shù)。比如我們把執(zhí)行的代碼改成這樣:
g = test() print(next(g)) g.send('abc') print(next(g)) print(next(g))
我們?cè)賮?lái)看執(zhí)行的結(jié)果,會(huì)發(fā)現(xiàn)是這樣的:
第一行val不再是None,而是我們剛剛傳入的abc了。
隊(duì)列調(diào)度
生成器每次在執(zhí)行到y(tǒng)ield語(yǔ)句之后都會(huì)自然掛起,我們可以利用這一點(diǎn)來(lái)當(dāng)做協(xié)程來(lái)調(diào)度。我們可以自己實(shí)現(xiàn)一個(gè)簡(jiǎn)易的隊(duì)列來(lái)模擬這個(gè)過(guò)程。
首先我們聲明一個(gè)雙端隊(duì)列,每次從隊(duì)列左邊頭部獲取任務(wù),調(diào)度執(zhí)行到掛起之后,放入到隊(duì)列末尾。相當(dāng)于我們用循環(huán)的方式輪詢執(zhí)行了所有任務(wù),并且這整個(gè)全程不涉及任何線程創(chuàng)建和銷毀的過(guò)程。
class Scheduler: def __init__(self): self._queue = deque() def new_task(self, task): self._queue.append(task) def run(self): while self._queue: # 每次從隊(duì)列左側(cè)獲取task task = self._queue.popleft() try: # 通過(guò)next執(zhí)行之后放入隊(duì)列右側(cè) next(task) self._queue.append(task) except StopIteration: pass sch = Scheduler() sch.new_task(test(5)) sch.new_task(test(10)) sch.new_task(test(8)) sch.run()
這個(gè)只是一個(gè)很簡(jiǎn)易的調(diào)度方法,事實(shí)上結(jié)合上yield from以及send功能,我們還可以實(shí)現(xiàn)出更加復(fù)雜的協(xié)程調(diào)度方式。但是我們也沒(méi)有必要一一窮盡,只需要理解最基礎(chǔ)的方法就可以了,畢竟現(xiàn)在我們使用協(xié)程一般也不會(huì)自己實(shí)現(xiàn)了,都會(huì)通過(guò)官方原生的工具庫(kù)來(lái)實(shí)現(xiàn)。
@asyncio.coroutine
在Python3.4之后的版本當(dāng)中,我們可以通過(guò)@asyncio.coroutine這個(gè)注解來(lái)將一個(gè)函數(shù)封裝成協(xié)程執(zhí)行的生成器。
在吸收了協(xié)程這個(gè)概念之后,Python對(duì)生成器以及協(xié)程做了區(qū)分。加上了@asyncio.coroutine注解的函數(shù)稱為協(xié)程函數(shù),我們可以用iscoroutinefunction()方法來(lái)判斷一個(gè)函數(shù)是不是協(xié)程函數(shù),通過(guò)這個(gè)協(xié)程函數(shù)返回的生成器對(duì)象稱為協(xié)程對(duì)象,我們可以通過(guò)iscoroutine方法來(lái)判斷一個(gè)對(duì)象是不是協(xié)程對(duì)象。
比如我把剛剛寫的函數(shù)上加上注解之后再來(lái)執(zhí)行這兩個(gè)函數(shù)都會(huì)得到True:
import asyncio @asyncio.coroutine def test(k): n = 0 while n < k: yield print('n = {}'.format(n)) n += 1 print(asyncio.iscoroutinefunction(test)) print(asyncio.iscoroutine(test(10)))
那我們通過(guò)注解將方法轉(zhuǎn)變成了協(xié)程之后,又該怎么使用呢?
一個(gè)比較好的方式是通過(guò)asynio庫(kù)當(dāng)中提供的loop工具,比如我們來(lái)看這么一個(gè)例子:
loop = asyncio.get_event_loop() loop.run_until_complete(test(10)) loop.close()
我們通過(guò)asyncio.get_event_loop函數(shù)創(chuàng)建了一個(gè)調(diào)度器,通過(guò)調(diào)度器的run相關(guān)的方法來(lái)執(zhí)行一個(gè)協(xié)程對(duì)象。我們可以run_until_complete也可以run_forever,具體怎么執(zhí)行要看我們實(shí)際的使用場(chǎng)景。
async,await和future
從Python3.5版本開始,引入了async,await和future。我們來(lái)簡(jiǎn)單說(shuō)說(shuō)它們各自的用途,其中async其實(shí)就是@asyncio.coroutine,用途是完全一樣的。同樣await代替的是yield from,意為等待另外一個(gè)協(xié)程結(jié)束。
我們用這兩個(gè)一改,上面的代碼就成了:
async def test(k): n = 0 while n < k: await asyncio.sleep(0.5) print('n = {}'.format(n)) n += 1
由于我們加上了await,所以每次在打印之前都會(huì)等待0.5秒。我們把a(bǔ)wait換成yield from也是一樣的,只不過(guò)用await更加直觀也更加貼合協(xié)程的含義。
Future其實(shí)可以看成是一個(gè)信號(hào)量,我們創(chuàng)建一個(gè)全局的future,當(dāng)一個(gè)協(xié)程執(zhí)行完成之后,將結(jié)果存入這個(gè)future當(dāng)中。其他的協(xié)程可以await future來(lái)實(shí)現(xiàn)阻塞。我們來(lái)看一個(gè)例子就明白了:
future = asyncio.Future() async def test(k): n = 0 while n < k: await asyncio.sleep(0.5) print('n = {}'.format(n)) n += 1 future.set_result('success') async def log(): result = await future print(result) loop = asyncio.get_event_loop() loop.run_until_complete(asyncio.wait([ log(), test(5) ])) loop.close()
在這個(gè)例子當(dāng)中我們創(chuàng)建了兩個(gè)協(xié)程,第一個(gè)協(xié)程是每隔0.5秒print一個(gè)數(shù)字,在print完成之后把success寫入到future當(dāng)中。第二個(gè)協(xié)程就是等待future當(dāng)中的數(shù)據(jù),之后print出來(lái)。
在loop當(dāng)中我們要調(diào)度執(zhí)行的不在是一個(gè)協(xié)程對(duì)象了而是兩個(gè),所以我們用asyncio當(dāng)中的wait將這兩個(gè)對(duì)象包起來(lái)。只有當(dāng)wait當(dāng)中的兩個(gè)對(duì)象執(zhí)行結(jié)束,wait才會(huì)結(jié)束。loop等待的是wait的結(jié)束,而wait等待的是傳入其中的協(xié)程的結(jié)束,這就形成了一個(gè)依賴循環(huán),等價(jià)于這兩個(gè)協(xié)程對(duì)象結(jié)束,loop才會(huì)結(jié)束。
到此,關(guān)于“如何理解Python中的協(xié)程”的學(xué)習(xí)就結(jié)束了,希望能夠解決大家的疑惑。理論與實(shí)踐的搭配能更好的幫助大家學(xué)習(xí),快去試試吧!若想繼續(xù)學(xué)習(xí)更多相關(guān)知識(shí),請(qǐng)繼續(xù)關(guān)注創(chuàng)新互聯(lián)網(wǎng)站,小編會(huì)繼續(xù)努力為大家?guī)?lái)更多實(shí)用的文章!
當(dāng)前題目:如何理解Python中的協(xié)程
URL標(biāo)題:http://aaarwkj.com/article18/gjgjgp.html
成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供網(wǎng)站改版、微信小程序、自適應(yīng)網(wǎng)站、品牌網(wǎng)站設(shè)計(jì)、微信公眾號(hào)、標(biāo)簽優(yōu)化
聲明:本網(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)