我正在研究Project Loom 的作業原理以及它可以為我的公司帶來什么樣的好處。
所以我理解動機,對于基于標準 servlet 的后端,總是有一個執行緒池來執行業務邏輯,一旦執行緒因 IO 被阻塞,它只能等待。因此,假設我有一個具有單個端點的后端應用程式,該端點背后的業務邏輯是使用內部使用 InputStream 的 JDBC 讀取一些資料,后者將再次使用阻塞系統呼叫(Linux 中的 read())。所以如果我有 20000 個用戶到達這個端點,我需要創建 200 個執行緒,每個執行緒等待 IO。
現在假設我將執行緒池切換為使用虛擬執行緒。根據 Ben Evans 在Going inside Java's Project Loom and virtual threads 一文中所說:
相反,當進行阻塞呼叫(例如 I/O)時,虛擬執行緒會自動放棄(或讓出)它們的載體執行緒。
因此,據我所知,如果我的作業系統執行緒數量等于 CPU 內核數量和無限數量的虛擬執行緒,則所有作業系統執行緒仍將等待 IO,并且執行程式服務將無法為虛擬執行緒分配新作業因為沒有可用的執行緒來執行它。它與常規執行緒有什么不同,至少對于作業系統執行緒,我可以將其擴展到千以增加吞吐量。還是我只是誤解了 Loom 的用例?提前致謝
添加在
我剛剛閱讀了這個郵件串列:
虛擬執行緒喜歡阻塞 I/O。如果執行緒需要阻塞說一個 Socket 讀取,那么這會釋放底層內核執行緒來做其他作業
我不確定我是否理解它,如果作業系統執行諸如讀取之類的阻塞呼叫,則作業系統無法釋放執行緒,出于這些目的,內核具有非阻塞系統呼叫,例如 epoll,它不會阻塞執行緒并立即回傳具有一些可用資料的檔案描述符串列。請問以上報價意味著引擎蓋下,JVM將取代阻塞read與非阻塞epoll,如果執行緒呼叫它是虛擬的?
uj5u.com熱心網友回復:
您的第一個摘錄缺少重要的一點:
相反,當進行阻塞呼叫(例如 I/O)時,虛擬執行緒會自動放棄(或讓出)它們的載體執行緒。這由庫和運行時處理[...]
這意味著:如果您的代碼對庫(例如 NIO)進行了阻塞呼叫,則庫檢測到您從虛擬執行緒呼叫它,并將阻塞呼叫轉換為非阻塞呼叫,停放虛擬執行緒并繼續處理其他一些虛擬執行緒代碼。
只有當沒有虛擬執行緒準備好執行時,本地執行緒才會被停放。
請注意,您的代碼從不呼叫阻塞系統呼叫,它會呼叫 Java 庫(當前執行阻塞系統呼叫)。Project Loom 替換了代碼和阻塞系統呼叫之間的層,因此可以做任何想做的事情——只要呼叫代碼的結果看起來相同。
uj5u.com熱心網友回復:
Thomas Kl?ger的回答是正確的。我會補充一些想法。
所以據我所知,如果我的作業系統執行緒數量等于 CPU 內核數量和無限數量的虛擬執行緒,所有作業系統執行緒仍將等待 IO
不,不正確,你誤會了。
您所描述的是在當前 Java 執行緒技術下發生的情況。通過 Java 執行緒到宿主 OS 執行緒的一對一映射,在 Java 中進行的任何阻塞呼叫(等待回應的時間相對較長)都會使宿主執行緒擺弄它的拇指,不做任何作業。如果主機有無數個執行緒,以便可以安排其他執行緒在 CPU 內核上作業,這將不是問題。但是主機作業系統執行緒非常昂貴,所以我們沒有無數,我們只有很少。
使用 Project Loom 技術,JVM 檢測阻塞呼叫,例如等待 I/O。一旦檢測到,JVM 就會在等待 I/O 回應時擱置(“停放”)虛擬執行緒。JVM 為該主機作業系統載體執行緒分配一個不同的虛擬執行緒,以便“真實”執行緒可以繼續執行作業,而不是在擺弄它的拇指時等待。由于 JVM 中的虛擬執行緒非常便宜(記憶體和 CPU 都非常高效),我們可以有數千甚至數百萬個執行緒供 JVM 處理。
在您的 200 個執行緒的示例中,每個執行緒等待 IO 回應形成對資料庫的 JDBC 呼叫,如果這些是虛擬執行緒,則它們都將停放在 JVM 中。您用作載體執行緒的少數主機作業系統執行緒將用于ExecutorService當前未被阻止的其他虛擬面包。JVM 中的 Project Loom 技術會自動處理這種先阻塞后解除阻塞的虛擬執行緒的停放和重新調度,無需我們 Java 應用程式開發人員的干預。
假設我將執行緒池切換為使用虛擬執行緒
實際上,沒有虛擬執行緒池。每個虛擬執行緒都是新鮮的,沒有回收。這消除了對螺紋區域污染的擔憂。
ExecutorService executorService = Executors.newVirtualThreadPerTaskExecutor() ;
…
executorService.submit( someTask ) ; // Every task submitted gets assigned to a fresh new virtual thread.
要了解更多資訊,我強烈建議您觀看 Project Loom 團隊成員 Ron Pressler 或 Alan Bateman 的演講和采訪視頻。查找最新的,因為 Loom 一直在發展。
并閱讀新的 Java JEP,JEP 草案:虛擬執行緒(預覽版)。
uj5u.com熱心網友回復:
我終于找到了答案。所以正如我所說,默認情況下,InputStream.read方法會進行read()系統呼叫,根據 Linux 手冊頁將阻止底層作業系統執行緒。那么 Loom 怎么可能不會阻止呢?我找到了一篇文章,顯示了堆疊跟蹤 So if this block of code will be execution by virtual thread
URLData getURL(URL url) throws IOException {
try (InputStream in = url.openStream()) {//blocking call
return new URLData(url, in.readAllBytes());
}
}
JVM 運行時會將其轉換為以下堆疊跟蹤
java.base/jdk.internal.misc.VirtualThreads.park(VirtualThreads.java:60)//this line parks the virtual thread
java.base/sun.nio.ch.NioSocketImpl.park(NioSocketImpl.java:184)
java.base/sun.nio.ch.NioSocketImpl.park(NioSocketImpl.java:212)
java.base/sun.nio.ch.NioSocketImpl.read(NioSocketImpl.java:356)//JVM runtime will replace an actual read() into read from java nio package
java.base/java.io.InputStream.readAllBytes(InputStream.java:346)
JVM 如何知道何時解除虛擬執行緒?這readAllBytes是完成后將運行的堆疊跟蹤
"Read-Poller" #16
java.base@17-internal/sun.nio.ch.KQueue.poll(Native Method)
java.base@17-internal/sun.nio.ch.KQueuePoller.poll(KQueuePoller.java:65)
java.base@17-internal/sun.nio.ch.Poller.poll(Poller.java:195)
文章作者使用MacOs,Mackqueue用作非阻塞系統呼叫,如果我在Linux上運行它,我會看到epoll系統呼叫。
所以基本上 Loom 沒有引入任何新東西,在引擎蓋下它是一個epoll帶有回呼的普通系統呼叫,可以使用諸如 Vert.x 之類的框架來實作,該框架在引擎蓋下使用 Netty,但在 Loom 中,回呼邏輯是用 JVM 運行時封裝的我發現這與直覺相反,當我呼叫 InputStream.read() 時,我確實希望有一個相應的 read() 系統呼叫,但 JVM 會用非阻塞系統呼叫替換它。
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/371829.html
