色哟哟视频在线观看-色哟哟视频在线-色哟哟欧美15最新在线-色哟哟免费在线观看-国产l精品国产亚洲区在线观看-国产l精品国产亚洲区久久

0
  • 聊天消息
  • 系統消息
  • 評論與回復
登錄后你可以
  • 下載海量資料
  • 學習在線課程
  • 觀看技術視頻
  • 寫文章/發帖/加入社區
會員中心
創作中心

完善資料讓更多小伙伴認識你,還能領取20積分哦,立即完善>

3天內不再提示

6種I/O模式告訴你協程的作用

Q4MP_gh_c472c21 ? 來源:碼農的荒島求生 ? 作者:陸小風 ? 2022-05-24 15:23 ? 次閱讀

大家好,我是小風哥,今天我們來聊聊協程的作用。

假設磁盤上有10個文件,你需要讀取的內存,那么你該怎么用代碼實現呢?

在接著往下看之前,先自己想一想這個問題,看看自己能想出幾種方法,各自有什么樣的優缺點。想清楚了嗎(還在看嗎),想清楚了我們繼續往下看。

最簡單的方法——串行這可能是大多數同學都能想到的最簡單方法,那就是一個一個的讀取,讀完一個接著讀下一個。用代碼表示是這樣的:
for file in files:  result = file.read()  process(result)
是不是非常簡單,我們假設每個文件讀取需要1分鐘,那么10個文件總共需要10分鐘才能讀取完成。這種方法有什么問題呢?實際上,這種方法只有一個問題,那就是除此之外,其它都是優點,比如:
  1. 代碼簡單,容易理解

  2. 可維護性好,這代碼交給誰都能維護的了(論程序員的核心競爭力在哪里)

那么,慢的問題又該怎么解決呢?有的同學可能已經想到了,為啥要一個一個讀取呢?并行讀取不就可以加快速度了嗎。
稍好的方法,并行那么,該怎么并行讀取文件呢?顯然,地球人都知道,線程就是用來并行的。我們可以同時開啟10個線程,每個線程中讀取一個文件。用代碼實現就是這樣的:

		def read_and_process(file): result = file.read() process(result) def main(): files = [fileA,fileB,fileC......] for file in files: create_thread(read_and_process, file).run() # 等待這些線程執行完成怎么樣,是不是也非常簡單。那么這種方法有什么問題嗎?在開啟10個線程這種問題規模下沒有問題。現在我們把問題難度加大,假設有10000個文件,需要處理該怎么辦呢?有的同學可能想10個文件和10000個文件有什么區別嗎,直接創建10000個線程去讀不可以嗎?實際上,這里的問題其實是說創建多個線程有沒有什么問題。我們知道,雖然線程號稱“輕量級進程”,雖然是輕量級但當數量足夠可觀時依然會有性能問題。這里的問題主要有這樣幾個方面:
  1. 創建線程需要消耗系統資源,像內存等(想一想為什么?)

  2. 調度開銷,尤其是當線程數量較多且都比較繁忙時(同樣想一想為什么?)

  3. 創建多個線程不一定能加快I/O(如果此時設備處理能力已經飽和)

既然線程有這樣那樣的問題,那么還有沒有更好的方法?答案是肯定的,并行編程不一定只能依賴線程這種技術,關于并發編程可以用哪些技術實現的詳細討論請參考《高性能服務器是如何實現的》。這里的答案就是基于事件驅動編程技術。
事件驅動 + 異步沒錯,即使在單個線程中,使用事件驅動+異步也可以實現IO并行處理,Node.js就是非常典型的例子。為什么單線程也可以做到并行呢?這是基于這樣兩個事實:
  1. 相對于CPU的處理速度來說,IO是非常慢的

  2. IO不怎么需要計算資源

因此,當我們發起IO操作后為什么要一直等著IO執行完成呢?在IO執行完之前的這段時間處理其它IO難道不香嗎這就是為什么單線程也可以并行處理多個IO的本質所在。回到我們的例子,該怎樣用事件驅動+異步來改造上述程序呢?實際上非常簡單。首先,我們需要創建一個event loop,這個非常簡單:

		event_loop = EventLoop()然后,我們需要往event loop中加入原材料,也就是需要監控的event,就像這樣:

		def add_to_event_loop(event_loop, file): file.asyn_read() # 文件異步讀取 event_loop.add(file)注意,當執行file.asyn_read這行代碼時會立即返回,不會阻塞線程,當這行代碼返回時可能文件還沒有真正開始讀取,這就是所謂的異步。file.asyn_read這行代碼的真正目的僅僅是發起IO,而不是等待IO執行完成。此后,我們將該IO放到event loop中進行監控,也就是event_loop.add(file)這行代碼的作用。一切準備就緒,接下來就可以等待event的到來了:

		while event_loop: file = event_loop.wait_one_IO_ready() process(file.result)我們可以看到,event_loop會一直等待直到有文件讀取完成(event_loop.wait_one_IO_ready())。這時,我們就能得到讀完的文件了,接下來處理即可。全部代碼如下所示:
 def add_to_event_loop(event_loop, file):   file.asyn_read() # 文件異步讀取   event_loop.add(file)
def main():files=[fileA,fileB,fileC...]  event_loop = EventLoop()  for file in files:      add_to_event_loop(event_loop, file)        while event_loop:     file = event_loop.wait_one_IO_ready()     process(file.result)

多線程 VS 單線程 + event loop接下來,我們看下程序執行的效果。在多線程情況下,假設有10個文件,每個文件讀取需要1秒,那么很簡單,并行讀取10個文件需要1秒。那么,對于單線程+event loop呢?我們再次看下event loop + 異步版本的代碼:

		def add_to_event_loop(event_loop, file): file.asyn_read() # 文件異步讀取 event_loop.add(file) def main(): files = [fileA,fileB,fileC......] event_loop = EventLoop() for file in files: add_to_event_loop(event_loop, file)  while event_loop: file = event_loop.wait_one_IO_ready() process(file.result)對于add_to_event_loop,由于文件異步讀取,因此該函數可以瞬間執行完成,真正耗時的函數其實就是event loop的等待函數,也就是這樣:

		file = event_loop.wait_one_IO_ready()我們知道,一個文件的讀取耗時是1秒,因此該函數在1s后才能返回,但是,但是,接下來是重點。但是,雖然該函數wait_one_IO_ready會等待1s,不要忘了,我們利用這兩行代碼同時發起了10個IO操作請求。
for file in files:  add_to_event_loop(event_loop, file)
因此,在event_loop.wait_one_IO_ready等待的1s期間,剩下的9個IO也完成了。也就是說,event_loop.wait_one_IO_ready函數只是在第一次循環時會等待1s,但此后的9次循環會直接返回,原因就在于剩下的9個IO也完成了因此,整個程序的執行耗時也是1秒。是不是很神奇,我們只用一個線程就達到了10個線程的效果。這就是event loop + 異步的威力所在。
一個好聽的名字:Reactors模式本質上,我們上述給出的event loop簡單代碼片段做的事情本質上和生物一樣:給出刺激,做出反應。我們這里的給出event,然后處理event。這本質上就是所謂的Reactors模式。現在你應該明白所謂的Reactors模式是怎么一回事了吧。所謂的一些看上去復雜的異步框架,其核心不過就是這里給出的代碼片段,只是這些框架可以支持更加復雜的多階段任務處理,以及各種類型的IO。而我們這里給出的代碼片段,只能處理文件讀取這一類IO。
把回調也加進來如果我們需要處理各種類型的IO上述代碼片段會有什么問題嗎?問題就在于上述代碼片段就不會這么簡單了,針對不同類型會有不同的處理方法。因此,上述process方法需要判斷IO類型然后有針對性的處理,這會使得代碼越來越復雜,越來越難以維護。幸好我們也有應對策略,這就是回調。關于回調函數,請參考這篇《程序員應如何理解回調函數》。我們可以把IO完成后的處理任務封裝到回調函數中,然后和IO一并注冊到event loop就像這樣:

		def IO_type_1(event_loop, io): io.start()  def callback(result): process_IO_type_1(result)  event_loop.add((io, callback))這樣,event_loop在檢測到有IO完成后就可以把該IO和關聯的callback處理函數一并檢索出來,直接調用callback函數就可以了。

		while event_loop: io, callback = event_loop.wait_one_IO_ready() callback(io.result)看到了吧,這樣event_loop內部就極其簡潔了,even_loop根本就不關心該怎么處理該IO結果,這是注冊的callback該關心的事情,event_loop需要做的僅僅就是拿到event以及相應的處理函數callback,然后調用該callback函數就可以了。現在我們可以同單線程來并發編程了,也使用callback對IO處理進行了抽象,使得代碼更加容易維護,想想看還有沒有什么問題?
		
回調函數的問題雖然回調函數使得event loop內部更加簡潔,但依然有其它問題,讓我們來仔細看看回調函數:

		def start_IO_type_1(event_loop, io): io.start()  def callback(result): process_IO_type_1(result)  event_loop.add((io, callback))從上述代碼中你能看到什么問題嗎?在上述代碼中,一次IO處理過程被分為了兩個部分:
  1. 發起IO

  2. IO處理

其中,第2部分放到了回調函數中,這樣的異步處理天然不容易理解,這和我們熟悉的發起IO,等待IO完成、處理IO結果的同步模塊有很大差別。這里的給的例子很簡單,所以你可能不以為意,但是當處理的任務非常復雜時,可能會出現回調函數中嵌套回調函數,也就是回調地獄,這樣的代碼維護起來會讓你懷疑為什么要稱為一名苦逼的碼農。
問題出在哪里讓我們再來仔細的看看問題出在了哪里?同步編程模式下很簡單,但是同步模式下發起IO,線程會被阻塞,這樣我們就不得不創建多個線程,但是創建過多線程又會有性能問題。這樣為了發起IO后不阻塞當前線程我們就不得不采用異步編程+event loop。在這種模式下,異步發起IO不會阻塞調用線程,我們可以使用單線程加異步編程的方法來實現多線程效果,但是在這種模式下處理一個IO的流程又不得不被拆分成兩部分,這樣的代碼違反程序員直覺,因此難以維護。那么很自然的,有沒有一種方法既能有同步編程的簡單理解又會有異步編程的非阻塞呢?答案是肯定的,這就是協程。關于協程請參考《程序員應如何理解協程》。
Finally!終于到了協程利用協程,我可以以同步的形式來異步編程。這是什么意思呢?我們之所以采用異步編程是為了發起IO后不阻塞當前線程,而是用協程,程序員可以自行決定在什么時刻掛起當前協程,這樣也不會阻塞當前線程。而協程最棒的一點就在于掛起后可以暫存執行狀態恢復運行后可以在掛起點繼續運行,這樣我們就不再需要像回調那樣將一個IO的處理流程拆分成兩部分了。因此,我們可以在發起異步IO,這樣不會阻塞當前線程,同時在發起異步IO后掛起當前協程,當IO完成后恢復該協程的運行。這樣一來,我們就可以實現同步的方式來異步編程了。接下來,我們就用協程來改造一下回調版本的IO處理方式:

		def start_IO_type_1(io): io.start() # IO異步請求 yield # 暫停當前協程  process_IO_type_1(result) # 處理返回結果此后,我們要把該協程放到event loop中監控起來:

		def add_to_event_loop(io, event_loop): coroutine = start_IO_type_1(io) next(coroutine) event_loop.add(coroutine)最后,當IO完成后event loop檢索出相應的協程并恢復其運行:

		while event_loop: coroutine = event_loop.wait_one_IO_ready() next(coroutine)現在你應該看出來了吧,上述代碼中沒有回調,也沒有把處理IO的流程拆成兩部分,整體的代碼都是以同步的方式來編寫,最棒的是依然能達到異步的效果。實際上你會看到,采用協程后我們依然需要基于事件編程的event loop,因為本質上協程并沒有改變IO的異步處理本質,只要IO是異步處理的那么我們就必須依賴event loop來監控IO何時完成,只不過我們采用協程消除了對回調的依賴,整體編程方式上還是采用程序員最熟悉也最容易理解的同步方式。
		
總結看上去簡簡單單的IO,實際上一點都不簡單。為了高效進行IO操作,我們采用的技術是這樣演進的:
  1. 單線程串行 + 阻塞式IO(同步)

  2. 多線程并行 + 阻塞式IO(并行)

  3. 單線程 + 非阻塞式IO(異步) + event loop

  4. 單線程 + 非阻塞式IO(異步) + event loop + 回調

  5. Reactor模式(更好的單線程 + 非阻塞式IO+ event loop + 回調)

  6. 單線程 + 非阻塞式IO(異步) + event loop + 協程

最終,我們采用協程技術獲取到了異步編程的高效以及同步編程的簡單理解,這也是當今高性能服務器常用的一種技術組合。希望這篇文章能對你理解高效IO有所幫助。

審核編輯 :李倩


聲明:本文內容及配圖由入駐作者撰寫或者入駐合作網站授權轉載。文章觀點僅代表作者本人,不代表電子發燒友網立場。文章及其配圖僅供工程師學習之用,如有內容侵權或者其他違規問題,請聯系本站處理。 舉報投訴
  • 代碼
    +關注

    關注

    30

    文章

    4858

    瀏覽量

    69553
  • 模式
    +關注

    關注

    0

    文章

    65

    瀏覽量

    13471

原文標題:6種I/O模式告訴你,協程到底有什么用?

文章出處:【微信號:gh_c472c2199c88,微信公眾號:嵌入式微處理器】歡迎添加關注!文章轉載請注明出處。

收藏 人收藏

    評論

    相關推薦

    I/O接口與I/O端口的區別

    在計算機系統中,I/O接口與I/O端口是實現CPU與外部設備數據交換的關鍵組件,它們在功能、結構、作用及運作機制上均存在顯著差異,卻又相互協
    的頭像 發表于 02-02 16:00 ?483次閱讀

    數據I/O模塊的概念、特點以及作用

    ? 本文簡單介紹了數據I/O模塊的概念、特點以及作用。 一、數據 I/O 模塊是什么 1. 承接內外數據交互的“橋梁” 數據
    的頭像 發表于 01-21 11:10 ?347次閱讀

    直接I/O

    電子發燒友網站提供《直接I/O庫.pdf》資料免費下載
    發表于 10-14 10:55 ?0次下載
    直接<b class='flag-5'>I</b>/<b class='flag-5'>O</b>庫

    物聯網中常見的I/O擴展電路設計方案_IIC I/O擴展芯片

    物聯網系統中為什么要使用 IIC I/O擴展芯片 ??在物聯網系統中使用IIC(也稱為I2C)I/O擴展芯片的原因主要可以歸結為以下幾點:
    的頭像 發表于 09-24 11:29 ?851次閱讀
    物聯網中常見的<b class='flag-5'>I</b>/<b class='flag-5'>O</b>擴展電路設計方案_IIC <b class='flag-5'>I</b>/<b class='flag-5'>O</b>擴展芯片

    保護I/O模塊免受浪涌事件的影響

    電子發燒友網站提供《保護I/O模塊免受浪涌事件的影響.pdf》資料免費下載
    發表于 09-21 10:14 ?0次下載
    保護<b class='flag-5'>I</b>/<b class='flag-5'>O</b>模塊免受浪涌事件的影響

    遠程I/O模塊在不同領域的應用

    模塊發揮著至關重要的作用。它們能夠實時收集生產線上各種設備的運行狀態和生產數據,并將這些信息傳輸至中控系統。這使得生產管理人員能夠實時掌握生產線的運行情況,及時調整生產策略,提高生產效率和產品質量。同時,遠程I/O模塊還降低
    的頭像 發表于 09-20 16:43 ?829次閱讀

    I/O模塊的主要作用有哪些

    遠程I/O模塊是一使能遠程數據采集和控制的設備。通過使用網絡技術,如現場總線、以太網等,遠程I/O模塊能夠將輸入和輸出信號傳遞給控制系統。
    的頭像 發表于 09-20 16:41 ?788次閱讀

    區域架構和 MCU I/O 擴展

    電子發燒友網站提供《區域架構和 MCU I/O 擴展.pdf》資料免費下載
    發表于 09-09 10:51 ?0次下載
    區域架構和 MCU <b class='flag-5'>I</b>/<b class='flag-5'>O</b> 擴展

    淺談如何克服FPGA I/O引腳分配挑戰

    ,” 以及“ Place I/O Ports Sequentially.”。每種模式提供了將I/O端口分配到引腳的不同分配方式。 利用這些
    發表于 07-22 00:40

    PLC的I/O點數是什么意思

    在工業自動化領域中,可編程邏輯控制器(PLC)扮演著至關重要的角色。PLC以其高可靠性、易編程性和強大的控制功能,廣泛應用于各種自動化系統中。而在PLC的性能參數中,I/O點數是一個不可忽視的重要指標。本文將對PLC的I/
    的頭像 發表于 06-27 11:15 ?5677次閱讀

    LM8328支持Keyscan I/O擴展PWM和ACCESS.bus的移動I/O伴侶數據表

    電子發燒友網站提供《LM8328支持Keyscan I/O擴展PWM和ACCESS.bus的移動I/O伴侶數據表.pdf》資料免費下載
    發表于 06-27 10:08 ?0次下載
    LM8328支持Keyscan <b class='flag-5'>I</b>/<b class='flag-5'>O</b>擴展PWM和ACCESS.bus的移動<b class='flag-5'>I</b>/<b class='flag-5'>O</b>伴侶數據表

    TCAN104xAV-Q1 具有 1.8V I/O支持和待機模式的汽車類雙路CAN FD收發器數據表

    電子發燒友網站提供《TCAN104xAV-Q1 具有 1.8V I/O支持和待機模式的汽車類雙路CAN FD收發器數據表.pdf》資料免費下載
    發表于 06-25 09:55 ?0次下載
    TCAN104xAV-Q1 具有 1.8V <b class='flag-5'>I</b>/<b class='flag-5'>O</b>支持和待機<b class='flag-5'>模式</b>的汽車類雙路CAN FD收發器數據表

    PLC的I/O模塊的作用及其重要性

    在工業自動化領域中,可編程邏輯控制器(PLC)扮演著至關重要的角色。作為PLC的核心組成部分,I/O(輸入/輸出)模塊不僅連接著PLC與外部設備,更是實現信息交換的關鍵橋梁。本文將詳細探討PLC的I/
    的頭像 發表于 06-19 10:43 ?3827次閱讀

    軟件可配置模擬 I/O 的設計理念

    作者: Kenton Williston 曾幾何時,模擬 I/O 就是最專業、功能最固定的硬件。例如,電流驅動器和電壓傳感器是完全不同的零件,試圖顛倒其角色可謂是荒謬至極。 軟件可配置模擬 I/
    的頭像 發表于 05-05 11:10 ?1059次閱讀
    軟件可配置模擬 <b class='flag-5'>I</b>/<b class='flag-5'>O</b> 的設計理念

    16路數字量輸入I/O模塊用于測量和控制

    16路數字量輸入I/O模塊M1161、M1162 16路數字量輸入模塊是EdgeIO I/O 系統的重要組成部分。16路數字量輸入I/
    的頭像 發表于 04-07 16:57 ?1048次閱讀
    16路數字量輸入<b class='flag-5'>I</b>/<b class='flag-5'>O</b>模塊用于測量和控制
    主站蜘蛛池模板: 永久精品视频无码一区 | 久久黄视频 | 暖暖视频大全免费观看 | 把极品白丝班长啪到腿软 | 国产成人在线视频播放 | 农民工老头在出租屋嫖老熟女 | 这里只有精品网 | 99草在线观看 | 国产99久久久国产精品成人 | 亚洲精品不卡在线 | 小黄飞二人转 | 办公室日本肉丝OL在线 | 久久久久久久99精品免费观看 | 精品视频免费在线 | 精品一区二区三区AV天堂 | 久久九九少妇免费看A片 | 媚药调教被撑到合不拢h | 国产亚洲精品久久精品录音 | 国产一区二区三区四区五在线观看 | 美女露出撒尿的部位 | 国产美女又黄又爽又色视频网站 | 2018国产天天弄谢 | 草莓西瓜樱桃香蕉直播视频 | 国产精品久久久久a影院 | 空姐内射出白浆10p 空姐厕所啪啪啪 | 黄色三级视频在线 | 美女被触手注入精子强制受孕漫画 | 国产人妻人伦精品98 | 饥渴难耐的浪荡艳妇在线观看 | 色偷偷亚洲天堂 | 调教椅上的调教SM总裁被调教 | 且试天下芒果免费观看 | 老阿姨才是最有V味的直播 牢记永久免费网址 | 色欲久久精品AV无码 | 国产精品99久久久久久动态图 | lesbabes性欧美| 国产成人国产在线观看入口 | 日本午夜精品一区二区三区电影 | 超碰在线vip | 4455永久在线毛片观看 | 70岁妇女牲交色牲片 |