Python協(xié)程

這幾天看了看操作系統(tǒng)计技,順便研究了一下Python的協(xié)程喜德,下面就是做的一點筆記


協(xié)程是什么?

協(xié)程,英文Coroutines酸役,是一種比線程更加輕量級的存在住诸。正如一個進程可以擁有多個線程一樣,一個線程也可以擁有多個協(xié)程涣澡。協(xié)程相當于一個特殊的函數(shù)贱呐,可以在某個地方掛起,并且可以重新在掛起處繼續(xù)運行入桂。
協(xié)程不被操作系統(tǒng)內(nèi)核所管理奄薇,而完全是由程序所控制(也就是在用戶態(tài)執(zhí)行),這樣帶來的好處就是性能得到了很大的提升,不會像線程切換那樣消耗資源抗愁。
一個線程內(nèi)的多個協(xié)程是串行執(zhí)行的馁蒂,不能利用多核,所以蜘腌,顯然沫屡,協(xié)程不適合計算密集型的場景。協(xié)程適合I/O 阻塞型撮珠。


Python的協(xié)程

Python的協(xié)程有這么三種

  1. 由生成器變形來的 yield/send
  2. Python 3.4版本引入的@asyncio.coroutine和yield from
  3. Python 3.5版本引入的async/await

生成器變形來的 yield/send

在寫這個之前沮脖,先要知道Python的生成器,講生成器之前又要先講下迭代器芯急。

迭代器

在Python中勺届,實現(xiàn)了iter方法的對象即為可迭代對象,可迭代對象能提供迭代器娶耍。
實現(xiàn)了next方法的對象稱為迭代器免姿,Python2里面是實現(xiàn)next方法。因為迭代器也屬于可迭代對象榕酒,所以說迭代器也要實現(xiàn)iter方法胚膊,即迭代器需要同時實現(xiàn)iternext兩個方法。

關(guān)于可迭代對象和迭代器的區(qū)別想鹰,參考https://blog.csdn.net/liangjisheng/article/details/79776008澜掩,這里就不多講了。

直接講迭代器了杖挣。

class Miracle:
    def __init__(self,name,age):
        self.__name = name
        self.__age = age
    def __next__(self):
        self.__age += 1
        if self.__age > 778:
            raise StopIteration
        return self.__age
    def set(self,age):
        self.__age = age
    def __iter__(self):
        return self

m = Miracle("Miracle778",770)

上面定義了一個類肩榕,實現(xiàn)了__next__、__iter__方法,故這個類的對象是迭代器株汉。迭代器可以調(diào)用內(nèi)置方法next(另一種寫法是obj.__next__())筐乳,進行一次迭代。我們?nèi)绻蒙厦娴拇a的上下文環(huán)境執(zhí)行 next(m)語句乔妈,會輸出771蝙云。

如果next方法被調(diào)用,但迭代器沒有值可以返回的話路召,就會引發(fā)一個StopIteration異常勃刨,我們這里手動拋出該異常,主要是為了避免無限迭代股淡。

我們可以知道身隐,使用next()方法,迭代器會返回它的下一個值唯灵,那如果我們需要遍歷所有迭代器的值贾铝,那豈不是要調(diào)用n次next()方法了嗎?但還好埠帕,可以使用for循環(huán)進行遍歷垢揩。

class Miracle:
    def __init__(self,name,age):
        self.__name = name
        self.__age = age
    def __next__(self):
        self.__age += 1
        if self.__age > 778:
            raise StopIteration
        return self.__age
    def set(self,age):
        self.__age = age
        
    def __iter__(self):
        return self

m = Miracle("ye",770)
for i in m:
    print(i)

輸出:
771
772
773
774
775
776
777
778

可以看到上面的for i in m,這里發(fā)生的過程如下

Python解釋器會在第一次迭代時自動調(diào)用iter(obj)敛瓷,之后的迭代會調(diào)用迭代器的next方法叁巨,for語句會自動處理最后拋出的StopIteration異常。

也就是說呐籽,也可以這樣寫for i in iter(m)锋勺,執(zhí)行結(jié)果與for i in m一樣。
此外绝淡,因為迭代器也屬于可迭代對象,于是也能使用內(nèi)置方法list(),將對象轉(zhuǎn)換成列表苍姜。

m.set(770)
t = list(m)
print(t)
輸出:
[771,772,773,774,775,776,777,778]

迭代器就簡單講到這里了牢酵。下面簡單講下生成器


生成器

生成器其實是一種特殊的迭代器,不過這種迭代器更加優(yōu)雅衙猪。它不需要再像上面的類一樣寫__iter__()和__next__()方法了馍乙,只需要一個yiled關(guān)鍵字。 生成器一定是迭代器(反之不成立)

def fib():
    print("正在執(zhí)行")
    prev, curr = 0, 1
    while True:
        yield curr
        prev, curr = curr, curr + prev

比如說上面的代碼垫释,函數(shù)fib里面出現(xiàn)了yield關(guān)鍵字丝格,則這個函數(shù)即為生成器。函數(shù)的返回值是一個生成器對象棵譬。當執(zhí)行f=fib()返回的是一個生成器對象显蝌,此時函數(shù)體中的代碼并不會執(zhí)行,只有顯示或隱示地調(diào)用next的時候才會真正執(zhí)行里面的代碼订咸。

image

如上圖曼尊,執(zhí)行了next后酬诀,函數(shù)里面代碼才被執(zhí)行,所以生成器更能節(jié)省內(nèi)存和CPU骆撇。

開頭說到瞒御,生成器也是一種特殊的迭代器,故生成器也可以被迭代神郊,如下圖肴裙。


image

從上面的例子可以看到,通過yield語句返回迭代值涌乳,整個過程如下

在 for 循環(huán)執(zhí)行時蜻懦,每次循環(huán)都會執(zhí)行 fib 函數(shù)內(nèi)部的代碼,執(zhí)行到 yield curr 語句時爷怀,fib 函數(shù)就返回一個迭代值阻肩,下次迭代時,代碼從 yield curr 的下一條語句繼續(xù)執(zhí)行运授,而函數(shù)的本地變量看起來和上次中斷執(zhí)行前是完全一樣的烤惊,于是函數(shù)繼續(xù)執(zhí)行,直到再次遇到 yield吁朦∑馐遥看起來就好像一個函數(shù)在正常執(zhí)行的過程中被 yield 中斷了數(shù)次,每次中斷都會通過 yield 返回當前的迭代值逗宜。

上面我們用到的都是使用yield語句拋出迭代值雄右,如:yield curr,其實yield語句也能用來賦值纺讲。

def consume():
    while True:
        i = yield 
        print("消費者",i)

看到上面這個例子中的i = yield語句擂仍,我們可以在函數(shù)外調(diào)用send函數(shù)對i進行賦值,不過在調(diào)用send函數(shù)之前熬甚,必須先要“激活”該生成器逢渔,使其執(zhí)行到yield語句處。
下面我們就來調(diào)用一下send函數(shù)乡括,運行一下肃廓。

def consume():
    print("consume函數(shù)被激活")
    while True:
        i = yield 
        print("消費者",i)

consumer = consume() # 生成器
next(consumer)  # 激活生成器,使其運行到y(tǒng)ield函數(shù)處
for i in range(7):
    consumer.send(i)
image

send函數(shù)跟next函數(shù)類似诲泌,把值傳輸進去盲赊,然后函數(shù)運行yield語句后面的代碼,直至再次遇上yield敷扫,然后中斷哀蘑,等待下一個send或者next函數(shù)的運行。
因此,send函數(shù)也能用于生產(chǎn)器激活递礼,不過激活的時候惨险,要send(None)才行,此外send函數(shù)的返回值和next一樣脊髓,都是yield拋出的當前迭代值辫愉。


yield/send 協(xié)程

上面講生成器最后舉的例子,可視為yield/send型協(xié)程将硝。
yield會使當前函數(shù)即協(xié)程暫停恭朗,這不同于線程的阻塞,協(xié)程的暫停完全由程序控制依疼,協(xié)程暫停的時候痰腮,CPU可以去執(zhí)行另一個任務(wù),等待所需要的值準備好后由send傳入再繼續(xù)執(zhí)行律罢。

@asyncio.coroutine和yield from型協(xié)程

這類協(xié)程和async/await差不多膀值,所以就不講了,直接講Python 3.5版的async/await

async/await

在Python3.5中引入的async和await,可以將他們理解成asyncio.coroutine/yield from的完美替身误辑。當然沧踏,從Python設(shè)計的角度來說,async/await讓協(xié)程表面上獨立于生成器而存在巾钉,將細節(jié)都隱藏于asyncio模塊之下翘狱,語法更清晰明了。

先看到async關(guān)鍵字的使用情況

  1. async def 定義協(xié)程函數(shù)或者異步生成器
  2. async for 異步迭代器
  3. async with 異步上下文管理器

先看到async def定義的函數(shù)的兩種情況

image

我們可以看到砰苍,圖中的A函數(shù)是用return返回的潦匈,類型為協(xié)程類型,B函數(shù)里面包含yield關(guān)鍵字赚导,類型為異步生成器茬缩。

所以以async def開頭聲明的,函數(shù)代碼里不含yield的是協(xié)程函數(shù)吼旧,含yield的是異步生成器凰锡。異步生成器可以用async for進行迭代。

import asyncio

async def Miracle():
    print('Miracle778')
    await asyncio.sleep(1)
    for i in range(666,677):
        yield i

async def get():
    m = Miracle()
    async for i in m:
        i+=100
        print(i)


loop = asyncio.get_event_loop()
loop.run_until_complete(get())
loop.close()

看到上面代碼黍少,最后三行是運行協(xié)程所需要的寡夹,后面會講处面,我們來看到Miracle函數(shù)里的yield部分厂置,如上面所說,這種函數(shù)是異步生成器魂角,可以用async for迭代(見get函數(shù))昵济,這里迭代了666 - 676 十個數(shù),get函數(shù)里會把這十個數(shù)加100然后打印。執(zhí)行結(jié)果如下圖


image

async for迭代異步生成器介紹好了访忿,下面我們再返回去看到協(xié)程函數(shù)瞧栗,剛剛的截圖里面,當我們輸入type(A())的時候海铆,Python shell有一個警告迹恐,RuntimeWarning: coroutine 'A' was never awaited
這個警告信息說協(xié)程A永遠不會等待卧斟,我們知道殴边,協(xié)程最大的特點就是,在運行過程中珍语,可以掛起然后去執(zhí)行別的任務(wù)锤岸,待所需的條件滿足后,再返回來執(zhí)行掛起位置后續(xù)代碼板乙。而掛起操作,前面的yield/send型協(xié)程靠的是yield語句,這里的async/await則是靠await語句了冻辩。所以每個async類型的協(xié)程函數(shù)里面驱显,應(yīng)該要有await語句,不然就失去特性了凡辱,和普通函數(shù)一樣戒职,沒有交替執(zhí)行。

上面把 async透乾、await兩個關(guān)鍵字介紹了下洪燥,還給了個代碼示例,里面用到了asyncio的一些函數(shù)乳乌。所以下面就要介紹這些函數(shù)了捧韵。講到這里,有必要放下asyncio庫的官方文檔
另外再放幾篇別的相關(guān)文章:
Python 的異步 IO:Asyncio 簡介
通讀Python官方文檔之協(xié)程汉操、Future與Task
Python3 異步協(xié)程函數(shù)async具體用法

想要詳細了解asyncio可以看上面幾個鏈接再来,我這里就只簡單講講自己用到的函數(shù)。
我們上面講到過磷瘤,協(xié)程不能直接運行芒篷,需要創(chuàng)建一個事件循環(huán)loop,再把協(xié)程注冊到事件循環(huán)轟中采缚,然后開啟事件循環(huán)才能運行针炉。這幾步分別涉及的函數(shù)有
1 、創(chuàng)建事件循環(huán)

loop = asyncio.get_event_loop()
asyncio.get_event_loop()
獲取當前事件循環(huán)扳抽。 如果當前 OS 線程沒有設(shè)置當前事件循環(huán)并且 set_event_loop() 還沒有被調(diào)用篡帕,asyncio 將創(chuàng)建一個新的事件循環(huán)并將其設(shè)置為當前循環(huán)殖侵。

2、 將協(xié)程注冊到事件循環(huán)并開啟

loop.run_until_complete(future)
運行直到 future ( Future 的實例 ) 被完成镰烧。
如果參數(shù)是 coroutine object 拢军,將被隱式調(diào)度為 asyncio.Task 來運行。
返回 Future 的結(jié)果 或者引發(fā)相關(guān)異常怔鳖。

在注冊事件循環(huán)的時候茉唉,其實是run_until_complete方法將協(xié)程包裝成為了一個任務(wù)(task)對象。所謂task對象是Future類的子類结执。保存了協(xié)程運行后的狀態(tài)赌渣,用于未來獲取協(xié)程的結(jié)果,關(guān)于Future昌犹、Task類的詳情請看上面鏈接
asyncio.ensure_future(coroutine) 和 loop.create_task(coroutine)都可以創(chuàng)建一個task
也就是說坚芜,注冊事件循環(huán)有三種寫法

# 寫法一  直接傳入coroutine obeject 類型參數(shù),隱式調(diào)度
import asyncio
async def mriacle():
   await xxxxx
loop = asyncio.get_event_loop()
loop.run_until_complete(miracle())
# 寫法二 使用asyncio.ensure_future(coroutine) 創(chuàng)建task傳入
async def mriacle():
  await xxxxx
loop = asyncio.get_event_loop()
task = asyncio.ensure_future(miracle())
loop.run_until_complete(task)
# 寫法三 使用loop.create_task(coroutine) 創(chuàng)建task傳入
async def mriacle():
  await xxxxx
loop = asyncio.get_event_loop()
task = loop.create_task(miracle())
loop.run_until_complete(task)

這里關(guān)于寫法二和寫法三做個解釋斜姥,寫法二和三鸿竖,都是將協(xié)程生成一個task對象。
而run_until_complete的參數(shù)是一個futrue對象铸敏。當傳入一個協(xié)程缚忧,其內(nèi)部會自動封裝成task,task是Future的子類杈笔。isinstance(task, asyncio.Future)將會輸出True闪水。

上面都是通過 run_until_complete 來運行 loop ,等到 future 完成蒙具,run_until_complete 也就返回了球榆。但還可以通過另一個函數(shù) loop.run_forever 運行l(wèi)oop。不同的是禁筏,該函數(shù)等到 future 完成不會自動退出持钉,得需要 loop.stop() 函數(shù)退出,且 loop.stop 函數(shù)不能寫在 loop.run_forever 后面篱昔,得寫在協(xié)程函數(shù)里每强。具體見下面例子

# 協(xié)程miracle await 0.1s test await 0.3s,miracle執(zhí)行的更快州刽,
# 調(diào)用完loop.stop后空执,事件循環(huán)結(jié)束,故test沒有執(zhí)行完畢就退出了
import asyncio

async def miracle():
    print("coroutine miracle start")
    await asyncio.sleep(0.1)
    print("miracle end")
    loop.stop()

async def test():
    print("test start")
    await asyncio.sleep(0.3)
    print("test end")
    

loop = asyncio.get_event_loop()
task = loop.create_task(miracle())
task2 = loop.create_task(test())
loop.run_forever()

輸出結(jié)果如下圖


image

多個協(xié)程情況
上面 run_until_complete 傳入的都只是一個task穗椅,當有多個協(xié)程的時候辨绊,該怎么傳入呢
答案是用 asyncio.wait() asyncio.gather()函數(shù)

task1 = asyncio.ensure_future(demo1())
task2 = loop.create_task(demo2())
tasks = [task1,task2]
# asyncio.wait()
loop.run_until_complete(asyncio.wait(tasks))
# asyncio.gather()
loop.run_until_complete(asyncio.gather(*tasks))

asyncio庫里還有一些用于 socket 編程函數(shù),之前做網(wǎng)絡(luò)編程實驗的時候簡單用過房待,具體見官方文檔邢羔,這里就不講了。

awaitable 對象

我們在上面舉的代碼例子里面的 await 后面都是加的asyncio.sleep函數(shù)或者是async def定義的協(xié)程桑孩,那么這個 await 右邊能加些什么類型的對象呢? 我們可以試一下拜鹤。


分隔線,以下幾個是錯誤示例流椒,錯誤示例敏簿,千萬不要這樣寫*


本來是想著,如果一個協(xié)程讀文件的時候宣虾,掛起讓給另一個協(xié)程進行計算惯裕,然后等文件讀完,返回去執(zhí)行绣硝。但是蜻势,想法是好的,寫起來難受鹉胖,踩了好多坑握玛,所以就有了下面幾個錯誤示例,大家笑笑就好甫菠。挠铲。

# 錯誤寫法一
import asyncio
import time 

async def Read():
    print("Read start")
    f = open("C:\\Users\\HP\\Desktop\\1.gif",'rb')
    await f.read()
    print("Read end")

async def Calculate():
    print("Calculate start")
    start = time.time()
    while time.time() - start < 2:
        pass                    # 模擬計算消耗時間2s
    await asyncio.sleep(0.1)
    print("Calculate end")

loop = asyncio.get_event_loop()

loop.run_until_complete(asyncio.wait([Read(),Calculate()]))

錯誤寫法一,不清楚 await 右邊能跟什么對象寂诱,所以直接 await f.read()了拂苹,于是有了報錯
TypeError: object bytes can't be used in 'await' expression,報錯信息說bytes型對象不能被用于await表達式痰洒,于是上官方文檔搜搜 await 語法瓢棒,找到了 PEP492文件 Coroutines with async and await syntax、英語版看不懂的這里還有個中文版可以結(jié)合參考參考PEP492翻譯版
PEP492里有一段話丘喻,描述了什么是 awaitable

image

翻譯一下就音羞,await只接受awaitable對象,awaitable對象是以下的其中一個

1仓犬、由原生協(xié)程函數(shù)返回的原生協(xié)程對象 即使用 async def 聲明的協(xié)程函數(shù)

2嗅绰、由裝飾器types.coroutine()裝飾的一個“生成器實現(xiàn)的協(xié)程”對象
這里解釋一下,"生成器實現(xiàn)的協(xié)程"就是指我們前面介紹的 yield from 這種類型
types.coroutune 是個裝飾器搀继,可以把生成器實現(xiàn)的協(xié)程 轉(zhuǎn)化為與原生協(xié)程相兼容的形式窘面,代碼如下

@types.coroutine
def miracle():
  i = yield from test()
  ··· 

3、實現(xiàn)了await方法的對象(await方法返回的一個迭代器)
在asyncio庫里面叽躯,有 Future 對象

4财边、在CPython C API,有tp_as_async.am_await函數(shù)的對象点骑,該函數(shù)返回一個迭代器(類似await方法)

報錯原因
如果在async def函數(shù)之外使用await語句酣难,會引發(fā)SyntaxError異常谍夭。這和在def函數(shù)之外使用yield語句一樣。
如果await右邊不是一個awaitable對象憨募,會引發(fā)TypeError異常紧索。

所以我們上面那樣,直接調(diào)用 await f.read() 會引發(fā)TypeError異常菜谣。那應(yīng)該怎么改呢?

回調(diào) —— await 普通函數(shù)的方式

awaitable對象就上面那么幾種情況珠漂,一個普通的函數(shù)的話,1跟2肯定就不符合了尾膊,只能是3——實現(xiàn)了__await__方法(class Future)
于是在官方文檔里查看 Future 這一部分媳危,發(fā)現(xiàn)有幾個跟回調(diào)相關(guān)的函數(shù)。然后看了幾個Demo后冈敛,我就寫出了下面這段'錯誤代碼'待笑,這段代碼其實并沒有錯,但是他的執(zhí)行時間跟順序執(zhí)行差不多抓谴,沒有達到讀文件的時候滋觉,cpu處理另一個計算任務(wù)的效果,原因等下在后面我會分析齐邦。

import asyncio 
import time
 
def Read(future, f):
    print("Read start")
    start = time.time()
    data = f.read()
    print("Read用時",time.time()-start)
    future.set_result("讀取完畢")
    f.close()
    
async def main(loop):
    print("main start")
    all_done = asyncio.Future() # 設(shè)置Future 對象
    f = open("G:\\迅雷下載\\xxx.rmvb",'rb') 
    # 1.76Gb椎侠,讀完時間大概20s左右
    loop.call_soon(Read, all_done, f)  # 安排Read函數(shù)盡快執(zhí)行,后面是傳入的參數(shù)
 
    result = await all_done  
    # 若future對象完成措拇,則會通過set_result函數(shù)設(shè)置并返回一個結(jié)果
    print('returned result: {!r}'.format(result))

async def miracle():
    print("miracle start")
    await asyncio.sleep(0.1) 
    print("miracle running")
    start = time.time()
    while time.time() - start < 20: # 模擬計算20s
        pass
    print("miracle end")
 
 
event_loop = asyncio.get_event_loop()
start = time.time()
try: 
     event_loop.run_until_complete(asyncio.wait([miracle(),main(event_loop)])) 
finally: 
     event_loop.close()
print("總用時",time.time()-start)

如上面代碼示我纪,這段代碼是對錯誤代碼1的改進,剛剛我們分析了await f.read()報錯的原因丐吓,并且找到了解決方法浅悉,那就是把要進行的操作封裝在一個函數(shù)里,這個函數(shù)要有一個接收Future對象的參數(shù)券犁,然后協(xié)程函數(shù)里面术健,用asyncio.Future() 創(chuàng)建一個新的 Future 對象,然后用 loop.call_soon() 傳入函數(shù)名及參數(shù)粘衬,安排我們之前封裝的函數(shù)執(zhí)行荞估,然后就可以 await 傳入的future對象,等待函數(shù)代碼處理完成稚新,通過 future.set_result() 設(shè)置并返回一個結(jié)果勘伺,await會接受這個結(jié)果。

所以我們可以得到 await 普通函數(shù)的方法褂删,使用回調(diào)飞醉。

本來以為解決了這個問題,程序就能按我設(shè)想的那樣子異步執(zhí)行屯阀,讀文件的時候掛起去執(zhí)行另一個協(xié)程的計算缅帘,等文件讀完后轴术,再返回去處理。但事實卻不是這樣钦无,本來按我的預(yù)想逗栽,給Read函數(shù)讀的一個 1.7G的文件,讀完需要20s左右铃诬,在讀的時候掛起去執(zhí)行miracle協(xié)程,miracle函數(shù)會空循環(huán)20s苍凛,然后再掛起趣席,這時候Read函數(shù)和miracle協(xié)程都差不多要結(jié)束了,總時間應(yīng)該是20多s醇蝴。然而下面是運行結(jié)果圖


image

可以看到宣肚,總運行時間為40s,沒有達到異步的效果悠栓。對于這個問題霉涨,我后面搜了好久,分析了一下惭适,得到結(jié)果如下

協(xié)程是用戶自己控制切換的時機,不再需要陷入系統(tǒng)的內(nèi)核態(tài)笙瑟。而線程和進程,每次阻塞癞志、切換都需要陷入系統(tǒng)調(diào)用往枷,先讓CPU跑操作系統(tǒng)的調(diào)度程序,然后再由調(diào)度程序決定該跑哪一個進程(線程)凄杯。
因為協(xié)程是用戶自己寫調(diào)度邏輯的错洁,對CPU來說,協(xié)程其實是單線程戒突,所以CPU不用去怎么調(diào)度屯碴、切換上下文。
因為我們這里main協(xié)程里用到了Python的內(nèi)置文件操作膊存,而Python內(nèi)置文件操作調(diào)用的時候都是阻塞的导而,上面也說了,協(xié)程可視為單線程隔崎,因而一旦協(xié)程出現(xiàn)阻塞嗡载,將會阻塞整個線程,所以執(zhí)行到讀文件操作時仍稀,協(xié)程被阻塞洼滚,CPU只能等待文件讀完。


所以說如果要達到上面的邊讀文件邊計算的目的技潘,還是用多線程遥巴。那么我們下面用多線程實現(xiàn)下

import threading
import time


def Read(f):
    print("讀文件線程開始\n")
    start = time.time()
    f.read()
    end = time.time() - start
    print("讀文件線程結(jié)束,耗時",end)

start_main = time.time()
f = open("G:\\迅雷下載\\xx.rmvb",'rb')
th = threading.Thread(target=Read,args=(f,))
th.start()
print("主線程計算開始")
start = time.time()
while time.time()- start < 20: #模擬計算20s
    pass
end = time.time()
print("主線程計算結(jié)束,耗時",end - start)
th.join()
print("總耗時",time.time()-start_main)

執(zhí)行結(jié)果如下圖


image

其實協(xié)程也可以調(diào)用多線程實現(xiàn)異步千康,通過loop.run_in_executor函數(shù)可以創(chuàng)建線程

import asyncio
import time

def Read(f):
    print("Read start\n")
    start = time.time()
    f.read()
    print("Read end 耗時",time.time()-start)

def miracle():
    # 模擬計算
    print("miracle start")
    start = time.time()
    while time.time() - start < 20:
        pass
    print("miracle end 耗時",time.time()-start)

async def main(f):
    loop = asyncio.get_event_loop()
    t = []
    t.append(loop.run_in_executor(None,Read,f))
    t.append(loop.run_in_executor(None,miracle))
    for i in t:
        await i
        
start = time.time()
f = open("G:\\迅雷下載\\xx.rmvb",'rb')
loop = asyncio.get_event_loop()
loop.run_until_complete(main(f))
print("總耗時",time.time()-start)

運行截圖如下


image

總結(jié)

這協(xié)程、異步铲掐、多線程拾弃、阻塞I/O、非阻塞I/O摆霉,都要搞暈了豪椿。。携栋。
就這樣吧搭盾,留個大概印象,日后碰到了再回過頭仔細研究婉支。鸯隅。。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末向挖,一起剝皮案震驚了整個濱河市蝌以,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌何之,老刑警劉巖跟畅,帶你破解...
    沈念sama閱讀 219,366評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異溶推,居然都是意外死亡碍彭,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,521評論 3 395
  • 文/潘曉璐 我一進店門悼潭,熙熙樓的掌柜王于貴愁眉苦臉地迎上來庇忌,“玉大人,你說我怎么就攤上這事舰褪〗哉睿” “怎么了?”我有些...
    開封第一講書人閱讀 165,689評論 0 356
  • 文/不壞的土叔 我叫張陵占拍,是天一觀的道長略就。 經(jīng)常有香客問我,道長晃酒,這世上最難降的妖魔是什么表牢? 我笑而不...
    開封第一講書人閱讀 58,925評論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮贝次,結(jié)果婚禮上崔兴,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好敲茄,可當我...
    茶點故事閱讀 67,942評論 6 392
  • 文/花漫 我一把揭開白布位谋。 她就那樣靜靜地躺著,像睡著了一般堰燎。 火紅的嫁衣襯著肌膚如雪掏父。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,727評論 1 305
  • 那天秆剪,我揣著相機與錄音赊淑,去河邊找鬼。 笑死仅讽,一個胖子當著我的面吹牛陶缺,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播何什,決...
    沈念sama閱讀 40,447評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼组哩,長吁一口氣:“原來是場噩夢啊……” “哼等龙!你這毒婦竟也來了处渣?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,349評論 0 276
  • 序言:老撾萬榮一對情侶失蹤蛛砰,失蹤者是張志新(化名)和其女友劉穎罐栈,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體泥畅,經(jīng)...
    沈念sama閱讀 45,820評論 1 317
  • 正文 獨居荒郊野嶺守林人離奇死亡荠诬,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,990評論 3 337
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了位仁。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片柑贞。...
    茶點故事閱讀 40,127評論 1 351
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖聂抢,靈堂內(nèi)的尸體忽然破棺而出钧嘶,到底是詐尸還是另有隱情,我是刑警寧澤琳疏,帶...
    沈念sama閱讀 35,812評論 5 346
  • 正文 年R本政府宣布有决,位于F島的核電站,受9級特大地震影響空盼,放射性物質(zhì)發(fā)生泄漏书幕。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,471評論 3 331
  • 文/蒙蒙 一揽趾、第九天 我趴在偏房一處隱蔽的房頂上張望台汇。 院中可真熱鬧,春花似錦、人聲如沸励七。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,017評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽掠抬。三九已至吼野,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間两波,已是汗流浹背瞳步。 一陣腳步聲響...
    開封第一講書人閱讀 33,142評論 1 272
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留腰奋,地道東北人单起。 一個月前我還...
    沈念sama閱讀 48,388評論 3 373
  • 正文 我出身青樓,卻偏偏與公主長得像劣坊,于是被迫代替她去往敵國和親嘀倒。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 45,066評論 2 355