簡體   English   中英

為什么 IO 的非阻塞異步單線程比某些應用程序的阻塞多線程更快

[英]Why is Non blocking asynchronous single-threaded faster for IO than blocking multi-threaded for some applications

它通過使用現實世界的比較幫助我理解事物,在這種情況下是快餐。

java,對於同步阻塞我的理解是一個線程處理的每個請求一次只能完成一個。 就像通過駕車點餐一樣,所以如果我排在第 10 位,我必須等待前面的 9 輛車。 但是,我可以打開更多線程,以便同時完成多個訂單。

在 javascript 你可以有異步非阻塞但單線程。 據我了解,發出了多個請求,這些請求立即被接受,但請求在返回之前的某個時間由某個后台進程處理。 我不明白這怎么會更快。 如果您同時訂購 10 個漢堡,則 10 個請求將立即發出,但由於只有一名廚師(單線程),創建 10 個漢堡仍然需要相同的時間。

我的意思是我理解為什么非阻塞異步單線程“應該”更快的推理,但我問自己的問題越多,我就越不理解它,這讓我不理解它。

對於包括 IO 在內的任何類型的應用程序,我真的不明白非阻塞異步單線程如何比同步阻塞多線程更快。

非阻塞異步單線程有時更快

那不太可能。 你從哪里得到這個?

在多線程同步 I/O 中,大致是這樣工作的:

操作系統和應用程序服務器平台(例如 JVM)共同創建 10 個線程。 這些是在 memory 中表示的數據結構,在內核/操作系統級別運行的調度程序將使用這些數據結構來告訴您的一個 CPU 內核“跳轉到”代碼中的某個點以運行它在那里找到的命令。

表示線程的數據結構或多或少包含以下項目:

  • 我們正在運行的指令在 memory 中的位置是什么
  • 整個“堆棧”。 如果某個 function 調用第二個 function,那么我們需要記住所有局部變量和我們在原始方法中所處的位置,以便當第二個方法“返回”時,它知道如何做。 例如,您的平均 java 程序可能有 ~20 個方法深度,所以這是本地變量的 20 倍,代碼中有 20 個位置需要跟蹤。 這都是在堆棧上完成的。 每個線程都有一個。 它們往往是整個應用程序的固定大小。
  • 在運行此代碼的核心的本地緩存中啟動了哪些緩存頁面?

線程中的代碼編寫如下:所有與“資源”交互的命令(比你的 CPU 慢幾個數量級;think.network 數據包、磁盤訪問等)被指定為立即返回請求的數據(僅如果您要求的一切都已經可用並且在內存中,則可能)。 如果那是不可能的,因為你想要的數據還不存在(假設攜帶你想要的數據的數據包仍在線路上,前往你的網卡),那么代碼只需要做一件事為“獲取我的網絡數據”提供動力 function:等待數據包到達並進入 memory。

為了不只是什么都不做,操作系統/CPU 將協同工作以獲取代表線程的數據結構,凍結它,找到另一個這樣的凍結數據結構,解凍它,然后跳轉到“我們把東西放在哪里”點代碼。

這是一個“線程切換”:核心 A 正在運行線程 1。現在核心 A 正在運行線程 2。

線程切換涉及移動一堆 memory:所有那些“實時”緩存頁面和那個堆棧,需要靠近那個核心才能讓 CPU 完成工作,所以這是一個 CPU 從 main memory 加載一堆頁面,這確實需要一些時間。 不是很多(納秒),但也不為零。 現代 CPU 只能對附近緩存頁中加載的數據進行操作(大小約為 64k 到 1MB,僅此而已,比您的 RAM 棒可以存儲的內容少一千倍以上)。

在單線程異步 I/O 中,大致是這樣工作的:

當然還有一個線程(所有事情都在一個運行中),但是這次有問題的應用程序根本不是多線程的。 相反,它本身創建了跟蹤多個傳入連接所需的數據結構,而且至關重要的是,用於請求數據的原語工作方式不同。 請記住,在同步情況下,如果代碼從 .network 連接請求下一堆字節,那么線程將“凍結”(告訴 kernel 找到其他工作要做),直到數據在那里。 在異步模式下,如果可用則返回數據,但如果不可用,function“給我一些數據”仍然返回:但它只是說。 對不起芽。 我有 0 個新字節給你。

應用程序本身將決定 go 在其他連接上工作,這樣,單個線程可以管理一堆連接:是否有連接 #1 的數據? 是的,太好了,我會處理這個。 不? 哦好的。 連接 #2 有數據嗎? 等等等等。

請注意,如果數據到達,比如說,連接#5,那么這個線程,為了完成處理這個傳入數據的工作,可能需要從 memory 加載一堆 state 信息,並且可能需要寫入它.

例如,假設您正在處理一幅圖像,一半的 PNG 數據在線上傳輸。 您不能用它做很多事情,所以這個線程將創建一個緩沖區並將一半的 PNG 存儲在其中。 當它跳到另一個連接時,它需要加載它已經獲得的圖像的 ~15%,並將剛到達網絡數據包中的圖像的 10% 添加到該緩沖區。

這個應用程序還導致一堆 memory 被移入和移出緩存頁面,所以在這個意義上它並沒有那么不同,如果你想一次處理 100k 的東西,你不可避免地會最終不得不將內容移入和移出緩存頁面。

那么區別是什么呢? 你能用油炸廚師的話來說嗎?

不是真的,不。 這只是數據結構。

關鍵區別在於哪些內容被移入和移出這些緩存頁面。

在異步的情況下,它正是您編寫的代碼想要緩沖的內容。 不多也不少。

在同步的情況下,它是“代表線程的數據結構”。

以 java 為例:這至少意味着該線程的整個堆棧。 也就是說,根據-Xss參數,大約有 128k 的數據。 因此,如果您有 10 萬個連接要同時處理,那么這些堆棧需要 12.8GB 的 RAM!

如果這些傳入圖像的大小真的只有 4k 左右,那么您可以使用 4k 緩沖區來完成它,如果您通過異步手動處理,則最多只需要 0.4GB 的 memory。

這就是 async 的好處所在:通過手動滾動緩沖區,您無法避免將 memory 移入和移出緩存頁面,但可以確保它是更小的塊。 那會更快。

當然,為了真正讓它更快,在異步 model 中存儲 state 的緩沖區需要很小(如果你需要在操作之前將 128k 保存到 memory 中,這沒什么意義,這就是那些堆棧已經有多大了),並且您需要一次處理很多事情(同時處理 10k+)。

我們沒有用匯編程序編寫所有代碼或者 memory 托管語言流行的原因是:處理此類問題既乏味又容易出錯。 除非好處很明顯,否則您不應該這樣做。

這就是為什么同步通常是更好的選擇,並且在實踐中通常實際上更快(那些操作系統線程調度程序是由專家編碼人員編寫的並且調整得非常好。你沒有機會復制他們的工作) - 整個'通過手推我的緩沖區我可以減少需要移動一噸的字節數。 事情需要大於損失。

另外,async 編程復雜 model。

在異步模式下,你永遠不能阻塞。 想做一個快速的數據庫查詢? 這可能會阻塞,所以你不能那樣做,你必須將代碼編寫為: 好的,啟動這個作業,這里有一些代碼在它返回時運行。 你不能“等待答案”,因為在異步領域,等待是不允許的。

在異步模式下,無論何時你請求數據,你都需要能夠處理只得到你想要的一半的數據。 在同步模式下,如果你要求 4k,你就會得到 4k。 事實上,您的線程可能會在此任務期間凍結,直到 4k 可用,這不是您需要擔心的事情,您編寫代碼就像它剛好在您要求時到達一樣,完成了。

Bbbuutt...炒廚師!

看,CPU 設計還不夠簡單,無法用這樣的餐廳來表示。

您正在將瓶頸從您的流程(漢堡訂購者)轉移到另一個流程(漢堡制造商)。

這不會使您的應用程序更快。

考慮單線程異步 model 時,真正的好處是您的進程在等待其他進程時不會被阻塞

換句話說,不要將 async 與fast相關聯,而是與free相關聯。 有空做其他工作。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM