Reentrant vs Thread-safe


Part 3: C 語言例子 (thread-safe function)

接下來, 我們來看一些 thread-safe 的例子: 首先是在 wiki 網站上的一個 Thread-safe 但不是 reentrant 的例子.

#include <pthread.h>

int increment_counter() {
    static int counter = 0;
    static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;

    pthread_mutex_lock(&mutex);

    // only allow one thread to increment at a time
    ++counter;
    // store value before any other threads increment it further
    int result = counter;

    pthread_mutex_unlock(&mutex);

    return result;
}

這個例子中的 increment_counter() 可以被多個執行緒呼叫而不會產生任何問題, 因為它用了一個 mutex 來保護 (同步) 所有對共用的靜態變數 counter 的存取. 但是如果中斷服務程式 ISR 也呼叫了 increment_counter(), 就會很容易使系統當掉. 原因是如果中斷發生在執行緒正呼叫 increment_counter() 時 (尤其是 mutex lock 和 unlock 之間), 那 ISR 將永遠等不到 mutex 被 unlock. 因為 CPU 接受中斷進入 ISR 後, 只有 ISR 完成, 才會回到執行緒. 記住: 中斷永遠比正常執行優先, 所以 ISR 要比執行緒或者是 OS 核心優先執行.


接著下來的例子是從 http://www.thegeekstuff.com/2012/07/c-thread-safe-and-reentrant/ 節錄整理來的, 一樣是一個 Thread-safe 但不是 reentrant 的例子.

例子中意圖要控制一個字元陣列 arr 的元素依序被各個執行緒佔用.

...
char arr[10];
int  index = 0;

int func(char c) {

    if (index >= sizeof(arr)) {
        printf("\n No storage\n");
        return -1;
    }

    arr[index] = c;
    index++;

    return index;
}
...

很明顯的, 上面的函數 func() 一但被多個執行緒呼叫執行, 就破功了. 它的問題有二個:

  1. 第12行及第13行之間不可以被別的執行緒插斷, 原因是 index 還沒來得及 +1 以保護剛存入陣列元素的字元變數 c .
  2. 第7行取出共用變數 index 來檢查, 但是萬一在第12~13行還沒執行前就被別的執行緒插斷, 等到回復執行之後變數 index 值很可能已經被更動了, 但是 CPU 暫存器中的拷貝卻沒有更新, 而接著執行的第12~13行就會覆蓋了已經被別的執行緒所佔用的陣列元素了.

所以上面的例子必需適當的修改 (這裡只用註解標記應該加入 mutex lock/unlock 的修改處), 如:

...
char arr[10];
int  index = 0;

int func(char c) {
    int tmp;

    /* Lock a mutex here */
    tmp = index;

    if (index >= sizeof(arr)) {
        /* unlock the mutex here */
        printf("\n No storage\n");
        return -1;
    }
    index++;
    /* unlock the mutex here */

    arr[tmp] = c;
    return tmp;
}
...

上例的修改可能比較不那麼好 (好看及好維護), 原因是 mutex lock/unlock 不對稱, 出現了 2 個 mutex unlock. 但它還有一個重點是: 先佔用資源 (index++), 再把值存入. 這麼作可以早一點點把 mutex 放開 (重要!). 改寫成下面這個樣子可能看起來好些 (一樣只用註解標記應該加入 mutex lock/unlock 的修改處).

...
char arr[10];
int  index = 0;

int func(char c) {
    int tmp = -1;

    /* Lock a mutex here */
    if (index < sizeof(arr))
        tmp = index++;
    /* unlock the mutex here */

    if (tmp < 0)
        printf("\n No storage\n");
    else
        arr[tmp] = c;

    return tmp;
}
...

修改過後的 func() 可以被多個執行緒呼叫執行而不會發生存放位置位置索引不一致的問題了. 請注意: 用了 mutex 就不是 reentrant function.


後面還有

    MagicJackTing 發表在 痞客邦 留言(1) 人氣()