asyncio(非同步io)

晨恆父發表於2020-10-12

基本概念


引入非同步
  1. 同步、非同步(執行緒間)
    所謂同步,就是發出一個功能的呼叫時,在沒有得到結果之前,該呼叫就不返回或繼續執行後續操作。簡單來說,同步就是必須一件一件事做,等前一件做完了才能做下一件事
    同步與非同步相對,當一個非同步呼叫發出後,呼叫者在沒有得到結果之前,就可以繼續執行後續操作。當這個呼叫完成後,一般通過狀態、通知和回撥來通知呼叫者。對於非同步呼叫,呼叫的返回並不受呼叫者控制。通知呼叫者的三種方式,具體如下:

    方式描述
    狀態即監聽被呼叫者的狀態(輪詢),呼叫者每個一定時間檢查一次,效率很低
    通知當被呼叫者執行完成後,發出通知告知被呼叫者,無需消耗太多效能
    回撥與通知類似,當被呼叫者執行完成後,會呼叫呼叫者提供的回撥函式
  2. 阻塞、非阻塞(執行緒內)
    阻塞和非阻塞兩個概念僅僅與等待訊息通知的狀態相關。跟同步、非同步沒有太大關係,也就是說阻塞與非阻塞主要是程式等待通知時的狀態來講的
    阻塞呼叫是指呼叫結果返回之前,當前執行緒會被掛起。呼叫執行緒只有在得到結果後才會返回
    非阻塞是指在不能立刻得到結果之前,該呼叫不會阻塞當前執行緒

  3. 組合

    描述
    同步阻塞傳送方請求之後一直等待響應。接收方處理請求時進行的IO操作如果不能等到返回結果,就一直等到返回結果,才響應傳送方
    同步非阻塞傳送方傳送請求之後一直等待。接收方處理請求時進行的IO操作如果不能得到結果,就立即返回,去做其他事。但是由於沒有得到請求結果,不響應傳送方,傳送方一直等待。當IO操作完成以後,將完成狀態和結果通知接收方,接收方響應傳送方,傳送方進入下一次請求過程
    非同步阻塞傳送方向接收方請求後,不等待響應,可以繼續其他工作。接收方處理請求時進行IO操作如果不能馬上得到結果,就一直等到返回結果後才響應傳送方
    非同步非阻塞傳送方向接收方傳送請求後,不等待響應,可以繼續其他工作。接收方處理請求時進行IO操作如果不能得到結果,也不等待,而是馬上返回去做其他的事。當IO操作完成後,將完成狀態和結果通知接收方,接收方在響應傳送方
引入協程
  1. 協程
    協程,又稱微執行緒,英文名Coroutine。
  2. 子程式
    在所有語言中都是層級呼叫,比如A呼叫B,B在執行過程中又呼叫了C,C執行完畢返回,B執行完畢返回,最後是A執行完畢。所以子程式呼叫是通過棧實現的,一個執行緒就是執行一個子程式。子程式呼叫總是一個入口,一次返回,呼叫順序是明確的。
  3. 多執行緒
    避免順序執行的方式之一是多執行緒,但是考慮到python語言的特性(GIL鎖),再執行計算密集型的任務時,多執行緒的執行效果反而變慢,再執行IO密集型的任務時候雖然有不錯的效能提升,但是依然會有執行緒管理與切換、同步的開銷等等(具體原因這裡不詳細說明,請參見相關的GIL說明)
  4. 協程優勢
  • 最大的優勢就是協程極高的執行效率。因為子程式切換不是執行緒切換,而是由程式自身控制,因此,沒有執行緒切換的開銷,和多執行緒比,執行緒數量越多,協程的效能優勢就越明顯
  • 就是不需要多執行緒的鎖機制,因為只有一個執行緒,也不存在同時寫變數衝突,在協程中控制共享資源不加鎖,只需要判斷狀態就好了,所以執行效率比多執行緒高很多
asyncio概念與框架
  1. 什麼是task任務
    Task用來併發排程的協程,即對協程函式的進一步包裝?那為什麼還需要包裝呢?因為單純的協程函式僅僅是一個函式而已,將其包裝成任務,任務是可以包含各種狀態的,非同步程式設計最重要的就是對非同步操作狀態的把控了。
  • 建立任務

    task = asyncio.create_task(coro())
    ## task = asyncio.ensure_future(coro()) 
    ## loop.create_future(coro()) # loop為迴圈物件
    ## loop.create_task(coro()) 
    
  • 獲取某一任務

    task=asyncio.current_task(loop=None) ## 返回正在執行的任務
    asyncio.all_tasks(loop=None) ## 返回還沒有結束的任務
    
  1. future物件
    Future是一個較低層的可等待(awaitable)物件,他表示的是非同步操作的最終結果,當一個Future物件被等待的時候,協程會一直等待,直到Future已經運算完畢。
    Future是Task的父類,一般情況下,已不用去管它們兩者的詳細區別,也沒有必要去用Future,用Task就可以了,返回 future 物件的低階函式的一個很好的例子是 loop.run_in_executor().

  2. 非同步函式獲取結果

  • 直接通過result獲取

    import asyncio
    import time
    async def hello1(a,b):
        print("hello world 01 begin")
        await asyncio.sleep(3)
        print("hello again 01 end")
        return a+b
    
    coroutine = hello1(10,5)
    loop = asyncio.get_event_loop()
    task = asyncio.ensure_future(coroutine)
    loop.run_until_complete(task)
    print('--------------------------')
    print(task.result())
    loop.close()
    
  • 通過呼叫回撥函式

    import asyncio
    import time
    async def hello1(a,b):
        print("hello world 01 begin")
        await asyncio.sleep(3)
        print("hello again 01 end")
        return a+b
    def callback(future):
        print(future.result())
    coroutine = hello1(10,5)
    loop = asyncio.get_event_loop()
    task = asyncio.ensure_future(coroutine)
    task.add_done_callback(callback)
    loop.run_until_complete(task)
    loop.close()
    
  1. 模板(3.7之前)
  • 無參無返回值

    import asyncio
    async def hello1():
        print("hello world 01 begin")
        await asyncio.sleep(3)
        print("hello again 01 end")
    
    async def hello2():
        print("hello world 02 begin")
        await asyncio.sleep(2)
        print("hello again 02 end")
    
    async def hello3():
        print("hello world 03 begin")
        await asyncio.sleep(1)
        print("hello again 03 end")
    def callback(future):
        print(future.result())
    
    loop = asyncio.get_event_loop() ## 建立事件迴圈
    tasks = [hello1(),hello2(),hello3()] ## 將多個協程函式包裝成任務列表
    loop.run_until_complete(asyncio.wait(tasks))## 通過事件迴圈執行
    loop.close()## 取消事件迴圈
    
  • 有參有返回值

    import asyncio
    async def hello1(a,b):
        print("hello world 01 begin")
        await asyncio.sleep(3)
        print("hello again 01 end")
        return a+b
    
    async def hello2(a,b):
        print("hello world 02 begin")
        await asyncio.sleep(2)
        print("hello again 02 end")
        return a-b
    
    async def hello3(a,b):
        print("hello world 03 begin")
        await asyncio.sleep(1)
        print("hello again 03 end")
        return a*b
    
    
    loop = asyncio.get_event_loop() ## 建立事件迴圈
    task1 = asyncio.ensure_future(hello1(10,2))
    task2 = asyncio.ensure_future(hello2(19,2))
    task3= asyncio.ensure_future(hello3(9,2))
    tasks = [task1,task2,task3]
    loop.run_until_complete(asyncio.wait(tasks))## 通過事件迴圈執行
    print(task1.result())
    print(task2.result())
    print(task3.result())
    loop.close()## 取消事件迴圈
    

4.1. 流程

  • 構造事件迴圈

    loop=asyncio.get_running_loop() #返回(獲取)在當前執行緒中正在執行的事件迴圈,如果沒有正在執行的事件迴圈,則會顯示錯誤;它是python3.7中新新增的
     
    loop=asyncio.get_event_loop() #獲得一個事件迴圈,如果當前執行緒還沒有事件迴圈,則建立一個新的事件迴圈loop;
     
    loop=asyncio.set_event_loop(loop) #設定一個事件迴圈為當前執行緒的事件迴圈;
     
    loop=asyncio.new_event_loop()  #建立一個新的事件迴圈
    
    
  • 包裝task

    
    task = asyncio.create_task(coro(引數列表))   # 這是3.7版本新新增的
    task = asyncio.ensure_future(coro(引數列表)) 
    
  • 執行

    loop.run_until_complete(asyncio.wait(tasks))  #通過asyncio.wait()整合多個task
    loop.run_until_complete(asyncio.gather(tasks))  #通過asyncio.gather()整合多個task
    loop.run_until_complete(task_1)  #單個任務則不需要整合
    loop.run_forever()  #但是這個方法在新版本已經取消,不再推薦使用,因為使用起來不簡潔
     
    使用gather或者wait可以同時註冊多個任務,實現併發,但他們的設計是完全不一樣的,主要區別如下:
    (1)引數形式不一樣
    gather的引數為 *coroutines_or_futures,即如這種形式
          tasks = asyncio.gather(*[task1,task2,task3])或者
          tasks = asyncio.gather(task1,task2,task3)
          loop.run_until_complete(tasks)
    wait的引數為列表或者集合的形式,如下
          tasks = asyncio.wait([task1,task2,task3])
          loop.run_until_complete(tasks)2)返回的值不一樣
    gather的定義如下,gather返回的是每一個任務執行的結果,
          results = await asyncio.gather(*tasks) 
    wait的定義如下,返回dones是已經完成的任務,pending是未完成的任務,都是集合型別
     done, pending = yield from asyncio.wait(fs)3)後面還會講到他們的進一步使用
    
  • 關閉

    loop.close()
    
  1. 模板(3.7之後)
  • 無參無返回值

    import asyncio
    import time
     
     
    async def hello1():
        print("Hello world 01 begin")
        await asyncio.sleep(3)  #模擬耗時任務3秒
        print("Hello again 01 end")
     
    async def hello2():
        print("Hello world 02 begin")
        await asyncio.sleep(2)   #模擬耗時任務2秒
        print("Hello again 02 end")
     
    async def hello3():
        print("Hello world 03 begin")
        await asyncio.sleep(4)   #模擬耗時任務4秒
        print("Hello again 03 end")
     
    async def main():
        results=await asyncio.gather(hello1(),hello2(),hello3())
        for result in results:
            print(result)     #因為沒返回值,故而返回None
     
    asyncio.run(main())
    
    
  • 有參有返回值

    import asyncio
    import time
     
     
    async def hello1(a,b):
        print("Hello world 01 begin")
        await asyncio.sleep(3)  #模擬耗時任務3print("Hello again 01 end")
        return a+b
     
    async def hello2(a,b):
        print("Hello world 02 begin")
        await asyncio.sleep(2)   #模擬耗時任務2print("Hello again 02 end")
        return a-b
     
    async def hello3(a,b):
        print("Hello world 03 begin")
        await asyncio.sleep(4)   #模擬耗時任務4print("Hello again 03 end")
        return a*b
     
    async def main():
        results=await asyncio.gather(hello1(10,5),hello2(10,5),hello3(10,5))
        for result in results:
            print(result)
     
    asyncio.run(main())
    
  1. gather 與 wait區別
  • gather

    import asyncio
    from pprint import pprint
    
    import random
    
    
    async def coro(tag):
        print(">", tag)
        await asyncio.sleep(random.uniform(1, 3))
        print("<", tag)
        return tag
    
    
    loop = asyncio.get_event_loop()
    
    group1 = asyncio.gather(*[coro("group 1.{}".format(i)) for i in range(1, 6)])
    group2 = asyncio.gather(*[coro("group 2.{}".format(i)) for i in range(1, 4)])
    group3 = asyncio.gather(*[coro("group 3.{}".format(i)) for i in range(1, 10)])
    
    all_groups = asyncio.gather(group1, group2, group3)
    
    results = loop.run_until_complete(all_groups)
    
    loop.close()
    
    pprint(results)
    

    組中的所有任務都可以通過呼叫group2.cancel()甚至all_groups.cancel()…。

  • wait

    import asyncio
    import random
    
    
    async def coro(tag):
        print(">", tag)
        await asyncio.sleep(random.uniform(0.5, 5))
        print("<", tag)
        return tag
    
    
    loop = asyncio.get_event_loop()
    
    tasks = [coro(i) for i in range(1, 11)]
    
    print("Get first result:")
    finished, unfinished = loop.run_until_complete(
        asyncio.wait(tasks, return_when=asyncio.FIRST_COMPLETED))
    
    for task in finished:
        print(task.result())
    print("unfinished:", len(unfinished))
    
    print("Get more results in 2 seconds:")
    finished2, unfinished2 = loop.run_until_complete(
        asyncio.wait(unfinished, timeout=2))
    
    for task in finished2:
        print(task.result())
    print("unfinished2:", len(unfinished2))
    
    print("Get all other results:")
    finished3, unfinished3 = loop.run_until_complete(asyncio.wait(unfinished2))
    
    for task in finished3:
        print(task.result())
    
    loop.close()
    

    支援在完成第一個任務後或在指定的超時之後等待停止,從而允許操作的精度降低:

相關文章