翻譯 異步I/O不會建立新的線程

異步I/O不會建立新的線程

本文翻譯自 Stephen Cleary 的 [There is No Thread] 原文地址 https://blog.stephencleary.com/2013/11/there-is-no-thread.htmlhtml

這是異步編程最基本的事實 : 異步I/O不會建立新的線程編程

反對這個事實的人不少,他們對此的見解是 "若是我await一個操做,那麼必定會有一個線程正在等待,它多是一個線程池的線程,或者是操做系統線程,或者是某個設備的驅動程序"c#

不用關注這些見解, 只須要記住若是異步操做是純粹的(方法全是async),那麼就不會產生新的線程api

不過這麼一個簡單的結論顯然並不可以讓他們信服,下面就看看異步到底發生了什麼網絡

讓咱們跟蹤一個異步操做直到硬件層面,注:Net部分和設備驅動部分將被簡化描述 (由於細節太多了)app

下面是一個通用的寫入操做 (例如 文件、網絡、USB麪包機等等)異步

private async void Button_Click(object sender, RoutedEventArgs e)
{
  byte[] data = ...
  await myDevice.WriteAsync(data, 0, data.Length);
}

咱們已經知道在await的時候UI線程不會被阻塞,那麼問題來了: 是不是UI線程建立了另一個線程殺了祭天換來了本身的自由....async

關於UI線程是否犯下這一惡行,讓咱們深刻推斷一下分佈式

首先 : 看看類庫 (BCL的代碼) 假設 WriteAsync 是使用.Net中標準的異步方式平臺調用(P/Invoke)I/O系統,這是一個基於異步方式的I/O,因此這會在設備的底層上啓動一個win32異步方式I/O操做句柄異步編程

而後操做系統會告訴設備驅動程序進行寫操做,代碼實現是構造出表示寫請求的對象,這個對象稱爲I/O請求包(IRP)

設備驅動程序接收到IRP後會向設備發出一個命令來寫出數據,若是這個設備支持直接內存存取技術(DMA),只需將緩衝區地址寫入設備寄存器便可,設備驅動程序能作到將IRP標記爲 "pending" 後交把控制權交還操做系統

在這裏發現了真相的核心所在 : 在處理IRP時,設備驅動是不容許阻塞的。這意味着若是IRP不能當即完成,那麼它必須異步處理,即便對於同步的api也是如此!在設備驅動級別,全部(有意義)的請求都是異步的

引用書籍裏的知識 "不管是什麼類型的I/O請求,應用程序向驅動程序發出的I/O操做都是異步執行的"

在IRP爲 "pending" 時,操做系統返回到類庫,類庫將未完成的任務返回到應用程序的按鈕單擊事件方法,該方法掛起async方法,UI線程繼續執行

咱們已經跟蹤請求到系統的盡頭 直到物理設備

如今寫入操做正在飛快的進行中,有多少個線程正在處理它?

答案是沒有

沒有設備驅動程序線程、操做系統線程、BCL線程或者寫在處理寫操做的線程池線程(🙂) 這裏沒有建立任何新的線程

如今,讓咱們跟蹤內核守護進程的響應回到正常的人類世界(被大神虐了)

寫入請求開始後的某個時間,設備完成了寫入操做。它會舉手報告中斷一下CPU

設備驅動程序的中斷服務程序 (ISR) 對中斷操做出響應,中斷是CPU級事件,它會臨時從正在運行的線程中奪取CPU的控制權。能夠將ISR看做 "借用" 當前正在運行的線程,但我(原做者)更傾向於認爲ISR很是低的級別上運行,以致於 "線程" 的概念還不存在 - 所以能夠這麼說,它們在線程的下方出現(在線程以前就有了ISR)

不管如何,ISR是正確的,所以它所作的就是告訴設備「謝謝您的中斷」,並對延遲過程調用(DPC)進行排隊

當CPU成功被中斷吸引注意力時,它會到DPCs(注: Distributed Process Control System 分佈式處理控制系統),DPCs也是在一很是低的級別中運行(很是底層),說是它是"線程"並不徹底正確,與ISR同樣,DPCs直接在CPU上運行,在線程系統的"下方"

DPC會把表示寫請求的IRP標記爲 "complete" ,可是 "completion"狀態僅存在於操做系統級別,進程有本身的內存空間因此咱們必須通知到它,所以操做系統將一個特殊內核態的異步過程調用(Asyncroneus Procedure Call 簡稱 APC) 排隊到擁有句柄的線程

因爲類庫/BCL使用了標準的異步方式平臺調用(P/Invoke)系統,因此它已經註冊了帶有I/O輸出完成端口(ICOP 注:I/Q Completion Port)的句柄,IOCP是線程池的一部分,所以簡單的借用I/O線程池線程來執行APC,APC通知Task已經完成

該Task已捕獲UI上下文,所以它不會直接在線程池線程上恢復異步方法,相反,它將該方法排隊到UI上下文中,UI線程會恢復執行該方法

所以,咱們看到在請求在被處理時沒有建立新的線程,當請求完成後,各類線程被"借用",或者有一些處理被簡單的排隊等候,這個處理一般是在毫秒級時間內完成 (例如 在線程池中運行的APC),甚至到微秒級(例如 ISR),可是沒有線程被阻塞,只是等待該請求完成

咱們在上面的樣例中使用了被簡化後最標準的方式,在實際開發中會比這個樣例複雜不少,可是核心的事實是同樣的

"必須有一個處理異步操做的線程" 並非事實

打破常規,不要識圖去找到這個 "異步線程",這是不可能的。相反,要去尋找真相:

沒有線程

相關文章
相關標籤/搜索