多執行緒互斥鎖訪問演算法(下)------Lamport演算法(麵包店演算法)

風箏丶發表於2017-11-08

你好!這裡是風箏的部落格,

歡迎和我一起交流。

Lamport麵包店演算法是解決多個執行緒併發訪問一個共享的單使用者資源的互斥問題的演算法。 由Leslie Lamport發明。

Lamport把這個併發控制演算法可以非常直觀地類比為顧客去麵包店採購:
已知有n位顧客要進入麵包店採購,安排他們按照次序在前臺登記一個簽到號碼。該簽到號碼逐次加1。
根據簽到號碼的由小到大的順序依次入店購貨。
完成購買的顧客在前臺把其簽到號碼歸0. 如果完成購買的顧客要再次進店購買,就必須重新排隊。
同時進入麵包店採購的兩個或兩個以上的顧客有可能得到相同的號碼。
多個顧客如果抓到相同的號碼, 則規定按照顧客名字的字典次序進行排序, 這裡假定顧客是沒有重名的。

在計算機系統中, 顧客就相當於執行緒,每個程式有一個唯一的標識,而入店購貨就是進入臨界區獨佔訪問該共享資源。由於計算機實現的特點,存在兩個執行緒獲得相同的簽到號碼的情況,這是因為兩個執行緒幾乎同時申請排隊的簽到號碼,這兩個執行緒讀到的號碼是完全一樣的。所以,該演算法規定如果兩個執行緒的排隊簽到號碼相等,則執行緒id號較小的具有優先權。

我在看一些文章時,發現有如下描述:

為了方便,定義如下符號:若a<c或a==c和b<d同時成立,(a,b)<(c,d)

但是我看了產生歧義,看了好一會才理解,應該這樣寫才好:

a<c或( a==c和b<d同時成立 ),即:(a < c) || ((a == c) && (b < d)),表示為(a,b)<(c,d)

這樣就好理解多了。

核心程式碼如下:

#define true    1
#define false   0
#define process_num 4//執行緒數目
int choosing[process_num]={false};
int number[process_num]={0};

int find_max(void)/*找出最大號碼*/
{
    int max=0;
    int i=0;
    for(;i<process_num;++i)
    {
        if(number[i]>max)
            max=number[i];
    }
    return max;
}

void enter_lock(int thread_id)
{
    int i=0;
    choosing[thread_id]=true;
    number[thread_id]=find_max()+1;/*選號碼*/
    choosing[thread_id]=false;
    for(;i<process_num;++i)
    {
        while(choosing[i]);/*等待其他執行緒選號碼*/
        while((number[i] != 0)&&
            ( (number[i] < number[thread_id]) || ((number[i] == number[thread_id]) && (i < thread_id)) ));/*阻塞,等待排程*/
    }
}

void exit_lock(int thread_id)
{
    number[thread_id]=0;/*釋放號碼*/
}

這就是lamport的核心演算法,當我們需要訪問臨界區(互斥資源)時:

void process_A(void)//執行緒0
{
    enter_lock(1);
    //臨界區
    //訪問資源
    exit_lock(1);
}
void process_B(void)//執行緒1
{
    enter_lock(2);
    //臨界區
    //訪問資源
    exit_lock(2);
}

我們可以舉例模擬一下:
有兩個執行緒A(thread_id=1)和B(thread_id=2),
情況一:
當執行緒A進入enter_lock(1);,選取的號碼為1(number[1]=1),此時,無其他執行緒佔用資源。
for迴圈遍歷時,i=1時,對應的是執行緒A的資料,第二個while不成立,執行緒A得到資源時。

情況二:
當執行緒A進入enter_lock(1);,選取的號碼為1(number[1]=1),此時,系統排程,執行緒B執行,執行緒B得到的號碼為2(number[2]=2)。
for迴圈遍歷時,i=1時,對應的是執行緒A的資料,第二個while為:
while((number[1] != 0)&&( (number[1] < number[2]) || ((number[1] == number[2]) && (1 < 2)) ));
可知,while條件成立,則阻塞,待執行緒A釋放資源時,才得以執行。

情況三:
當執行緒B進入enter_lock(2);,選取的號碼為1(number[2]=1),此時,系統排程,執行緒A執行,執行緒A得到的號碼為2(number[1]=2)。
for迴圈遍歷時,i=2時,對應的是執行緒B的資料,第二個while為:
while((number[2] != 0)&&( (number[2] < number[1]) || ((number[2] == number[1]) && (2 < 1)) ));
可知,while條件成立,則阻塞,待執行緒B釋放資源時,才得以執行。

這樣,每個執行緒只寫它自己的choosing[thread_id]、number[thread_id],只讀取其它執行緒的這兩個資料項。
這個演算法不需要基於硬體的原子(atomic)操作實現,即它可以純軟體實現。
解決了peterson演算法的侷限,使得多個執行緒能參與互斥競爭。
多執行緒互斥鎖訪問演算法(上)——Peterson演算法

相關文章