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