跳至內容

忙碌等待

本頁使用了標題或全文手工轉換
維基百科,自由的百科全書

軟件工程中,忙碌等待(也稱自旋;英語:Busy waiting、busy-looping、spinning)是一種以行程反覆檢查一個條件是否為真為根本的技術,條件可能為鍵盤輸入或某個鎖是否可用。忙碌等待也可以用來產生一個任意的時間延遲,若系統沒有提供生成特定時間長度的方法,則需要用到忙碌等待。不同的電腦處理器速度差異很大,特別是一些處理器設計為可能根據外部因素(例如作業系統上的負載)動態調整速率。因此,忙碌等待這種時間延遲技術容易產生不可預知、甚至不一致的結果,除非實現代碼來確定處理器執行「什麼都不做」迴圈的速度,或者迴圈代碼明確檢查即時時鐘

在某些情況下,忙碌等待是有效的策略,特別是實現自旋鎖設計的作業系統上執行對稱多處理。不過一般來說,忙碌等待是應該避免的反模式[1],處理器時間應該用來執行其他任務,而不是浪費在無用的活動上。

對於多核CPU,忙碌等待的優點是不切換線程,避免了由此付出的代價。因此一些多線程同步機制不使用切換到內核態的同步對象,而是以用戶態的自旋鎖或其衍生機制(如輕型讀寫鎖)來做同步,付出的時間複雜度相差3個數量級。忙碌等待可使用一些機制來降低CPU功耗,如Windows系統中呼叫YieldProcessor,實際上是呼叫了SIMD指令_mm_pause。[來源請求]

C語言的範例程式

以下的C語言程式示範二個線程共用一個全域變數i,第一個線程用忙碌等待來確認變數i的值是否有改變。

#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
#include <stdlib.h>

volatile int i = 0; /* i is global, so it is visible to all functions.
                       It's also marked volatile, because it
                       may change in a way which is not predictable by the compiler,
                       here from a different thread. */

/* f1 uses a spinlock to wait for i to change from 0. */
static void *f1(void *p) {
    while (i == 0) {
        /* do nothing - just keep checking over and over */
    }
    printf("i's value has changed to %d.\n", i);
    return NULL;
}

static void *f2(void *p) {
    sleep(60);   /* sleep for 60 seconds */
    i = 99;
    printf("t2 has changed the value of i to %d.\n", i);
    return NULL;
}

int main() {
    int rc;
    pthread_t t1, t2;
    rc = pthread_create(&t1, NULL, f1, NULL);
    if (rc != 0) {
        fprintf(stderr, "pthread f1 failed\n");
        return EXIT_FAILURE;
    }
    rc = pthread_create(&t2, NULL, f2, NULL);
    if (rc != 0) {
        fprintf(stderr, "pthread f2 failed\n");
        return EXIT_FAILURE;
    }
    pthread_join(t1, NULL);
    pthread_join(t2, NULL);
    puts("All pthreads finished.");
    return 0;
}

上述的程式也可以用C11標準中的條件變數達成。

忙碌等待的替代品

大多數作業系統和線程庫提供了各種各樣可以阻止事件過程的系統呼叫,如鎖取得、計時器變化,I/O可用性或訊號。使用系統呼叫來產生延遲會有最簡單、最有效、公平且沒有競爭危害的結果。一個呼叫會檢查、通知事件等待的排程程式,插入一個適用的記憶障礙,也可以在返回之前執行所請求的I / O操作。當呼叫者被堵住時,其他行程可以使用CPU。排程器有實現優先級繼承所需的資訊或其他機制,來避免資源衰竭英語Resource starvation的問題。

在大部份作業系統中,也可以在忙碌等待中加入延遲函數(sleep()),以減少忙碌等待浪費的CPU資源。這可以讓線程暫停指定的時間,在此期間線程不會浪費CPU時間。如果迴圈檢查只是檢查一些簡單的事務,將大部分時間花費在延遲函數,則不太會浪費太多CPU時間。

若程式永遠不會結束(如作業系統),可以通過無條件跳轉(例如NASM語法中的jmp $)實現無限次的忙碌等待。CPU會永遠無條件跳轉到程式現在執行到的位置。因此可以用以下的程式取代忙碌等待:

sleep:
hlt
jmp sleep

適當的使用忙碌等待

一些底層編程中可能需要用到需忙碌等待。對每個硬件裝置(尤其是偶爾才使用到的硬件)設置中斷可能不切實際甚至不可能。有時需要將某種的控制數據寫入硬件,在寫入後取得裝置狀態,但狀態可能要在寫入後數個機器週期後才有效。程式設計師可以呼叫作業系統延遲函數,不過這樣做可能要耗費更多的時鐘周,此時就可以使用忙碌等待。

相關條目

參考資料