當前位置: 妍妍網 > 碼農

Python升級之路( Lv30) 並行編程三劍客(下)

2024-05-13碼農

Python系列文章目錄


第二十五章 Pygame遊戲開發基礎(下)

第二十六章 基於pygame 實作的坦克大戰

第二十七章 並行編程初識

第二十八章 並行編程三劍客(上)

第二十九章 並行編程三劍客(中)

第三十章 並行編程三劍客(下)


  • Python系列文章目錄

  • 前言

  • 協程

  • 協程與執行緒的比較

  • asyncio實作協程(重點)


  • 前言

    大家好, 我是了不起, 歡迎收看我的冒險之旅

    今天我們將學習並行編程三劍客之一的協程, 了解與行程執行緒的區別, 以及協程的建立

    在米斯特的解讀下(因為日誌使用的暗精靈語), 二人發現摩根的日誌記載了異次元裂縫的存在,確認了使徒狄瑞吉是造成傳染病的原因,並推斷出諾伊佩拉的狄瑞吉只是狄瑞吉在傳送過程中一部份能量泄露出來所產生的幻影。同時,摩根還在日誌中記錄了暴戾搜捕團的行蹤,並且推斷出愛麗絲有問題。而這些將會在不久的將來粉墨登場...

    協程

    協程也叫作纖程(Fiber),是一種線上程中,比執行緒更加輕量級的存在,由程式設計師自己寫程式來管理. 我們可以將協程理解為執行線上程上的程式碼塊, 協程掛起並不會引起執行緒阻塞, 他的作用是提高執行緒的利用率… 協程之間可以依靠信箱來進行通訊和數據共享, 了避免記憶體共享數據而帶來的執行緒安全問題. 因為其輕量和高利用率的特點, 即使建立上千個執行緒也不會對系統造成很大負擔, 而執行緒則恰恰相反. 協程是一種設計思想,不僅僅局限於某一門語言. 在Go, Java, Python 等語言中均有實作

    協程的優點

  • 由於自身帶有上下文和棧,無需執行緒上下文切換的開銷,屬於程式級別的切換,作業系統完全感知不到,因而更加輕量級;

  • 無需原子操作的釘選及同步的開銷;

  • 方便切換控制流,簡化編程模型

  • 單執行緒內就可以實作並行的效果,最大限度地利用cpu,且可延伸性高,成本低(註:一個CPU支持上萬的協程都不是問題。所以很適合用於高並行處理)

  • 協程的缺點

  • 無法利用多核資源:協程的本質是個單執行緒,它不能同時將 單個CPU 的多個核用上,協程需要和行程配合才能執行在多CPU上

  • 協程與執行緒的比較

  • 在單執行緒同步模型中,任務按照順序執行 如果某個任務因為I/O而阻塞,其他所有的任務都必須等待,直到它完成之後它們才能依次執行

  • 多執行緒模型中,多個任務分別在獨立的執行緒中執行 這些執行緒由作業系統來管理,在多處理器系統上可以並列處理,或者在單處理器系統上交錯執行. 這使得當某個執行緒阻塞在某個資源的同時其他執行緒得以繼續執行

  • 協程版本的程式中,多個任務交錯執行,但仍然在一個單獨的執行緒控制中 當處理I/O或者其他昂貴的操作時,註冊一個回呼到事件迴圈中,然後當I/O操作完成時繼續執行。回呼描述了該如何處理某個事件. 事件迴圈輪詢所有的事件,當事件到來時將它們分配給等待處理事件的回呼函式。

  • asyncio實作協程(重點)

  • 正常的函式執行時是不會中斷的,所以你要寫一個能夠中斷的函式,就需要加 async

  • async 用來聲明一個函式為異步函式,異步函式的特點是能在函式執行過程中掛起,去執行其他 異步函式,等到掛起條件(假設掛起條件是 sleep(5) )消失後,也就是5秒到了再回來執行

  • await 用來用來聲明程式掛起,比如異步程式執行到某一步時需要等待的時間很長,就將此掛 起,去執行其他的異步程式。

  • asyncio 是python3.5之後的協程模組,是python實作並行重要的包,這個包使用事件迴圈驅動實 現並行

  • asyncio協程是寫爬蟲比較好的方式. 比多執行緒和多行程都好.開辟新的執行緒和行程是非常耗時的

  • 實操程式碼

    1. 不使用協程時

      # 不使用協程執行多個任務
      import time

      deffun1():
      for i in range(3):
      print(f'原子彈:第{i}次爆炸啦')
      time.sleep(1)
      return"fun1執行完畢"

      deffun2():
      for k in range(3):
      print(f'氫彈:第{k}次爆炸了')
      time.sleep(1)
      return"fun2執行完畢"

      defmain():
      fun1()
      fun2()

      if __name__ == "__main__":
      start_time = time.time()
      main()
      end_time = time.time()
      print(f"耗時{end_time - start_time}") # 不使用協程,耗時6秒


    2. 使用使用yield協程,實作工作切換

      # 不使用協程執行多個任務
      import time

      deffun1():
      for i in range(3):
      print(f'原子彈:第{i}次爆炸啦')
      yield# 只要方法包含了yield,就變成一個生成器
      time.sleep(1)
      return"fun1執行完畢"

      deffun2():
      g = fun1() # fun1是一個生成器,fun1()就不會直接呼叫,需要透過next()或for迴圈呼叫
      print(type(g))
      for k in range(3):
      print(f'氫彈:第{k}次爆炸了')
      next(g) # 繼續執行fun1的程式碼
      time.sleep(1)
      return"fun2執行完畢"

      defmain():
      fun1()
      fun2()

      if __name__ == "__main__":
      start_time = time.time()
      main()
      end_time = time.time()
      print(f"耗時{end_time - start_time}") # 耗時5.0秒,效率差別不大


    3. 使用asyncio異步IO的典型使用方式實作協程

      實作步驟:

      實操程式碼

      # 不使用協程執行多個任務
      import asyncio
      import time

      asyncdeffun1():# async表示方法是異步的
      for i in range(3):
      print(f'原子彈:第{i}次爆炸啦')
      # await異步執行func1方法
      await asyncio.sleep(1)
      return"fun1執行完畢"

      asyncdeffun2():
      for k in range(3):
      print(f'氫彈:第{k}次爆炸了')
      # await異步執行func2方法
      await asyncio.sleep(1)
      return"fun2執行完畢"

      asyncdefmain():
      res = await asyncio.gather(fun1(), fun2())
      # 返回值為函式的返回值列表,本例為["func1執行完畢", "func2執行完畢"]
      print(res)
      if __name__ == "__main__":
      start_time = time.time()
      asyncio.run(main())
      end_time = time.time()
      print(f"耗時{end_time - start_time}") # 耗時3秒,效率極大提高


      實操結果 從這裏可以看到, 這裏進用了3.02s左右, 只比理論最短用時3s多了0.02s左右, 從這裏可以看出使用協程的巨大優勢

  • 建立兩個異步方法fun1, fun2

  • 建立一個main方法來管理上面兩個異步方法 await asyncio.gather(fun1(), fun2())

  • 主函式中透過 asyncio.run(main()) 來執行main方法

  • 今日冒險片段下

    根據日誌的提示, 二人確實找到了挪移佩拉地區, 並遇到了一個像黑色野豬的怪物, 由於是使徒的一絲殘影, 由於時間的流逝, 其攻擊已經非常的弱了, 但是二人仍然花費一天時間將其擊敗. 但擊敗狄瑞吉的幻影也足以讓了不起成功晉升至lv31.
    二人思考再三, 便將其交給摩根日記中的好友克倫特. 克倫特至此開始懷疑愛麗絲是整個事件的幕後指使,卻苦於沒有證據,只能罷手. 不過克倫特卻憑借直覺發現盜屍者有與偽裝者相近的氣息,懷疑二者實為同宗,即偽裝者與盜屍者本質都跟使徒脫不開關系,公國的聖職者曾經經歷過與偽裝者的暗黑聖戰,於是克倫特托冒險家咨詢歌蘭蒂斯. 起初歌蘭蒂斯並不相信類似偽裝者的東西會再次出現,而當冒險家拿來「變異的心臟」時,歌蘭蒂斯信了. 並且在此之後, 由此還引發了第二次暗黑聖戰...
    同時在歌蘭蒂斯那裏了不起還了解到, 萬年雪山附近一頭冰龍暴動, 襲擊了四劍聖之一的布萬加所在的村莊, 而布萬加也莫名失蹤. 想到曾經幫助過自己的人遇到了危險, 了不起便決定出發, 去解救被困的朋友.

    戳藍字 Python都知道 關註 我哦!

    PS Python都知道技術交流群(技術交流、摸魚、白嫖課程為主)又不定時開放了,感興趣的朋友,可以在下方公號內回復: 666 ,即可進入。

    老規矩 ,道友們還記得麽, 右下角的 「在看」 點一下 如果感覺文章內容不錯的話,記得分享朋友圈讓更多的人知道!