簡體   English   中英

什么是多線程環境中的忙自旋?

[英]What is busy spin in a multi-threaded environment?

什么是多線程環境中的“Busy Spin”?

它如何有用以及如何在多線程環境中用 java 實現?

它在哪些方面有助於提高應用程序的性能?

其他一些答案忽略了忙等待的真正問題。

除非您談論的應用程序涉及節省電力,否則消耗 CPU 時間本身並不是一件壞事。 只有當有其他一些線程或進程准備好運行時,這才是糟糕的。 當准備運行的線程之一是忙等待循環正在等待的線程時,這真的很糟糕。

這才是真正的問題。 在普通操作系統上運行的普通用戶模式程序無法控制哪些線程在哪些處理器上運行,普通操作系統無法區分忙於等待的線程和正在工作的線程之間的區別,並且即使操作系統知道線程正在忙等待,它也無法知道線程在等待什么。

因此,忙碌的服務員完全有可能等待許多毫秒(實際上是永恆),等待一個事件,而唯一可以使事件發生的線程坐在邊線(即,在運行隊列中)等待輪到使用CPU了。

忙等待通常用於嚴格控制哪些線程在哪些處理器上運行的系統中。 當您知道導致事件發生的線程實際上在不同的處理器上運行時,忙等待可能是等待事件的最有效方式。 當您為操作系統本身編寫代碼時,或者當您編寫在實時操作系統下運行的嵌入式實時應用程序時,通常就是這種情況。


凱文·沃爾特斯 (Kevin Walters) 寫過等待時間很短的案例。 在普通操作系統上運行的受 CPU 限制的普通程序可能被允許在每個時間片中執行數百萬條指令。 因此,如果程序使用自旋鎖來保護僅由幾條指令組成的臨界區,那么任何線程在處於臨界區時都不太可能丟失其時間片。 這意味着,如果線程A發現自旋鎖鎖定,則很可能該線程B,保持所述鎖,實際上一個不同的CPU上運行。 這就是為什么當您知道它將在多處理器主機上運行時,在普通程序中使用自旋鎖是可以的。

忙等待或自旋是一種技術,其中進程重復檢查條件是否為真,而不是調用等待或睡眠方法並釋放 CPU。

1.它主要用於多核處理器,條件很快就會成立,即毫秒或微秒

2.不釋放CPU的好處是,所有緩存的數據和指令不受影響,可能會丟失,如果這個線程在一個內核上掛起並帶回另一個線程

Busy spin 是一種在不釋放 CPU 的情況下等待事件的技術。 通常這樣做是為了避免丟失 CPU 緩存中的數據,如果線程在其他內核中暫停和恢復,則數據會丟失。

因此,如果您在一個低延遲系統上工作,您的訂單處理線程當前沒有任何訂單,而不是休眠或調用wait() ,您可以循環然后再次檢查隊列中是否有新消息。 只有當您需要等待非常短的時間(例如微秒或納秒)時,它才有用。

LMAX Disrupter 框架是一個高性能的線程間消息傳遞庫,它有一個 BusySpinWaitStrategy,它基於這個概念,並為在屏障上等待的 EventProcessor 使用忙碌的自旋循環。

“忙旋轉”在一個線程中不斷循環,以查看另一個線程是否已完成某些工作。 這是一個“壞主意”,因為它只是在等待而消耗資源。 最繁忙的旋轉甚至沒有睡眠,而是盡可能快地旋轉等待工作完成。 直接通過工作完成通知等待線程並讓它休眠直到那時浪費更少。

請注意,我稱之為“壞主意”,但它在某些情況下用於低級代碼以最小化延遲,但這在 Java 代碼中很少(如果有的話)需要。

從性能的角度來看,忙於旋轉/等待通常是一個壞主意。 在大多數情況下,最好在准備好跑步時睡覺並等待信號,而不是進行旋轉。 假設有兩個線程,線程 1 正在等待線程 2 設置變量(例如,它一直等到var == true 。然后,它會忙

while (var == false)
    ;

在這種情況下,您將占用線程 2 可能正在運行的大量時間,因為當您醒來時,您只是在無意識地執行循環。 因此,在您等待此類事情發生的情況下,最好讓線程 2 擁有所有控制權,讓您自己進入睡眠狀態,並在完成后讓它喚醒您。

但是,在極少數情況下,您需要等待的時間很短,實際上使用 spinlock更快 這是因為執行信令功能需要時間; 如果使用的旋轉時間少於執行信令所需的時間,則旋轉是可取的。 因此,以這種方式它可能是有益的,並且實際上可以提高性能,但這絕對不是最常見的情況。

Spin Waiting 是您不斷等待條件成立。 相反的是等待信號(如notify() 和wait() 引起的線程中斷)。

有兩種等待方式,第一種是半主動(sleep/yield)和主動(忙等待)。

在忙等待時,程序會使用特殊操作碼(如 HLT 或 NOP 或其他耗時操作)主動空閑。 其他用途只是一個 while 循環檢查條件是否成立。

JavaFramework 提供了 Thread.sleep、Thread.yield 和 LockSupport.parkXXX() 方法讓一個線程交出 CPU。 Sleep 等待特定的時間量,但即使指定了納秒,也總是需要超過一毫秒。 LockSupport.parkNanos(1) 也是如此。 Thread.yield 允許我的示例系統(win7 + i5 mobile)的分辨率為 100ns。

yield 的問題在於它的工作方式。 如果系統被完全利用,在我的測試場景中可能需要 800 毫秒(100 個工作線程都不確定地計數一個數字 (a+=a;))。 由於yield 釋放cpu 並將線程添加到其優先級組內所有線程的末尾,因此yield 因此不穩定,除非cpu 未被使用到一定程度。

忙等待將阻塞 CPU(核心)數毫秒。

Java 框架(檢查 Condition 類實現)使用小於 1000ns(1 微秒)的活動(忙)等待時間。 在我的系統中,System.nanoTime 的平均調用需要 160ns,所以忙於等待就像檢查條件在 nanoTime 上花費 160ns 並重復。

所以基本上 Java 的並發框架(隊列等)有類似在微秒旋轉下等待並在 N 粒度內達到等待周期,其中 N 是檢查時間約束的納秒數並等待一毫秒或更長時間(對於我目前的系統)。

因此,主動忙等待增加了利用率,但有助於系統的整體反應性。

在消耗 CPU 時間的同時,應使用特殊指令來降低執行耗時操作的內核的功耗。

忙自旋只不過是循環直到線程完成。 例如,您說有 10 個線程,並且您想等待所有線程完成然后想繼續,

while(ALL_THREADS_ARE_NOT_COMPLETE);
//Continue with rest of the logic

例如在 Java 中,您可以使用ExecutorService管理多個線程

    ExecutorService executor = Executors.newFixedThreadPool(10);
    for (int i = 0; i < 10; i++) {
        Runnable worker = new WorkerThread('' + i);
        executor.execute(worker);
    }
    executor.shutdown();
    //With this loop, you are looping over till threads doesn't finish.
    while (!executor.isTerminated());

這是一個忙碌的旋轉,因為它消耗資源,因為 CPU 並不理想,而是繼續在循環中運行。 我們應該有機制來通知主線程(父線程)來指示所有線程都完成了,它可以繼續執行剩余的任務。

在前面的示例中,您可以使用不同的機制來提高性能,而不是繁忙的旋轉。

   ExecutorService executor = Executors.newFixedThreadPool(10);
    for (int i = 0; i < 10; i++) {
        Runnable worker = new WorkerThread('' + i);
        executor.execute(worker);
    }
    executor.shutdown();
    try {
           executor.awaitTermination(Long.MAX_VALUE, TimeUnit.NANOSECONDS);
    } catch (InterruptedException e) {
        log.fatal("Exception ",e);
    }

暫無
暫無

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

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