1. 程式人生 > >多執行緒程式設計之資料訪問互斥——原理性

多執行緒程式設計之資料訪問互斥——原理性

  在多執行緒存在的環境中,除了堆疊中的臨時資料之外,所有的資料都是共享的。如果我們需要執行緒之間正確地執行,那麼務必需要保證公共資料的執行和計算是正確的。簡單一點說,就是保證資料在執行的時候必須是互斥的。否則,如果兩個或者多個執行緒在同一時刻對資料進行了操作,那麼後果是不可想象的。

  保證多執行緒之間的資料訪問互斥,有以下四類方法:

  (1)關中斷

  (2)數學互斥方法

  (3)作業系統提供的互斥方法

  (4)CPU原子操作

  下面針對這四種方法進行詳細說明:

(1)關中斷

  既然多執行緒之間的中斷切換會導致訪問同一資料的不同步,那麼關閉執行緒中斷切換肯定能過避免這個問題。而且,Intel X86系列CPU中確實存在這樣的關閉中斷指令。參照如下程式碼:

複製程式碼
#include <stdio.h>  
int main()  
{  
    __asm{  
        cli  
        sti  
    }  
    return 1;  
}
複製程式碼

  其中cli是關中斷,sti是開中斷。這段程式碼沒有什麼問題,可以編過,當然也可以生成執行檔案。但是在執行的時候會出現一個異常告警:Unhandled exception in test.exe: 0xC0000096:  Privileged Instruction。告警已經說的很清楚了,這是一個特權指令。只有系統或者核心本身才可以使用這個指令。

    不過,大家也可以想象一下。因為平常我們編寫的程式都是應用級別的程式,要是每個程式都是用這些程式碼,那不亂了套了。比如說,你不小心安裝一個低質量的軟體,說不定什麼時候把你的中斷關了,這樣你的網路就斷了,你的輸入就沒有迴應了,你的音樂什麼都沒有了,這樣的環境你受的了嗎?應用層的軟體是千差萬別的,軟體的水平也是參差不齊的,所以系統不可能相信任何一個私有軟體,它相信的只是它自己。簡單來說,作為應用程式開發,這個方法肯定是不可取的。

(2)資料方法

  通過某個數學演算法,可以確保不同的執行緒之間只可能其中一個訪問某個資料。例如有兩個執行緒操作同一個變數,可以採用如下演算法:

複製程式碼
unsigned int flag[2] = {0};
unsigned int turn = 0;

void process(unsigned int index)
{
    flag[index] = 1;
    turn =  1 - index;

    while(flag[1 - index] && (turn == (1 - index)));
    do_something();
    flag[index] 
= 0; }
複製程式碼

  其實,學過作業系統的朋友都知道,上面的演算法其實就是Peterson演算法,可惜它只能用於兩個執行緒的資料互斥。當然,這個演算法還可以推廣到更多執行緒之間的互斥,那就是bakery演算法。但是數學演算法有兩個缺點:

    a)佔有空間多,兩個執行緒就要flag佔兩個單位空間,那麼n個執行緒就要n個flag空間;

    b)程式碼編寫複雜,考慮的情況比較複雜。

(3)作業系統提供的互斥方法

  系統提供的互斥演算法其實是我們平時開發中用的最多的互斥工具。就拿windows來說,關於互斥的工具就有臨界區、互斥量、訊號量等等。這類演算法有一個特點,那就是都是依據系統提高的互斥資源,那麼系統又是怎麼完成這些功能的呢?其實也不難。

  舉一個最簡單的系統鎖實現方法:

複製程式碼
void Lock(HANDLE hLock)
{
    __asm {cli};

    while(1){
        if(/* 鎖可用*/){
            /* 設定標誌,表明當前鎖已被佔用 */
            __asm {sti};
            return;
        }

        __asm{sti};
        schedule();
        __asm{cli};
    }
}

void UnLock(HANDLE hLock)
{
    __asm {cli};
    /* 設定標誌, 當前鎖可用 */
    __asm{sti};
}
複製程式碼

  從程式碼中可以看出,採用的CPU的中斷關閉與開啟指令就能夠實現一個簡單的系統鎖。不過這個例子沒有考慮就緒執行緒的壓棧等問題,實際情況會更加複雜些。

(4)CPU原子操作

  在多執行緒中經常會涉及到一個經常用到而又非常簡單的計算操作,這個時候使用互斥量、訊號量等實現互斥操作顯得不划算。因此,CPU廠商將一些常用的操作設計成原子指令,在Windows系統中也稱之為原子鎖。常用的原子操作包括:

複製程式碼
InterLockedAdd
InterLockedExchange
InterLockedCompareExchange
InterLockedIncrement
InterLockedDecrement
InterLockedAnd
InterLockedOr
複製程式碼