如何理解Python中的協(xié)程

這篇文章主要介紹“如何理解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é)果:

如何理解Python中的協(xié)程

輸出的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)是這樣的:

如何理解Python中的協(xié)程

第一行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)

搜索引擎優(yōu)化