APUE讀書筆記 執行緒互斥(互斥量 讀寫鎖)

2021-08-15 18:06:49 字數 3045 閱讀 4674

這裡以檔案的引用計數做例子。

什麼是引用計數?

即指向這個檔案的鏈結數。 只有當引用計數為0時,才能刪除該檔案,否則每一次刪除僅僅是i節點的引用計數減一。

如果不引入鎖, 那麼當同時執行兩次引用計數相減時,就會出現計數不準確的情況。

一、簡單的鎖操作

#include "apue.h"

#include #include struct foo;

struct foo *foo_alloc(int id)

} return (fp);

}void foo_hold(struct foo *fp)

void foo_rele(struct foo *fp)

else

pthread_mutex_unlock(&fp->lock);

}

由於每次只使用到乙個資源,暫時不會發生死鎖。

但如果每次要請求2個foo,  那麼2個程序,請求2個foo時,若順序不同,就會發生死鎖。

二、預防死鎖

如果我們的檔案i節點根據其檔案id,被放在乙個雜湊鍊錶中。

1.當我們根據i節點指標增加引用計數時, 直接把i節點進行加鎖即可。

2.當我們根據檔案id進行增加計數時,需要先遍歷鍊錶,此時鍊錶需要加鎖。找到節點後進行計數增加,此時節點加鎖。(注意,這個過程是先 對鍊錶加鎖,再對i節點加鎖)

3.當我們根據i節點指標,決定刪除i節點時, 會先對i節點加鎖,然後讀取引用計數,發現要被減為0,決定刪除,這個刪除涉及到鍊錶中的節點刪除。 

注意!!這時候不可以直接對鍊錶加鎖並進行刪除,  因為這個步驟和2步驟會發生死鎖! 所以此時需要先解i節點鎖,在對鍊錶加鎖,加完鍊錶鎖之後,再去加i節點鎖,保證這個加鎖順序。

但是!!正因為這個取消鎖的過程,導致中間可能發生了其他變化,故又不得不進行計數判斷。。。

可以看出,以上的加鎖策略會引發非常大的麻煩,盡量不要出現加鎖解鎖又加鎖的情況,因為解鎖到加鎖的過程中,會出現其他的問題。

於是程式進行了改進, 第1步和第2步中,都直接對鍊錶加鎖(對最大的加鎖,以絕後患),不對i節點加鎖。

第3步中,也是先對大鍊錶加鎖, 找到對應的fp節點後,再進行對應的刪除操作。

#include "apue.h"

#include #include #define nhash 29

#define hash(id) (((unsigned long)id)%nhash)

struct foo *fh[nhash];

pthread_mutex_t hashlock = pthread_mutex_initializr;

struct foo;

struct foo *foo_alloc(int id)

idx = hash(id); //用key取餘法取雜湊

pthread_mutex_lock(&hashlock);

fp->f_next = fh[idx]; //雜湊鍊錶的頭插法,插在idx對應的佇列上。

fh[idx] = fp;

pthread_mutex_lock(&fp->f_lock);

//為什麼要先上fp鎖再解hash鎖? 為了防止剛解完雜湊鎖,有人插隊。

pthread_mutex_unlock(&hashlock);

/******執行fp的初始化操作****/

pthread_mutex_unlock(&fp->f_lock);

} return (fp);

}void foo_hold(struct foo *fp)

struct foo *foo_find(int id)

} pthread_mutex_unlock(&hashlock);

return fp;

}void foo_rele(struct foo *fp)else

pthread_mutex_unlock(&hashlock);

pthread_mutex_destroy(&fp->f_lock);

free(fp);

} else

pthread_mutex_unlock(&hashlock);

}

從上面的改進中可以看出, 加鎖不能隨便加,例如不能因為我修改的是i節點,就直接對i節點加鎖,而不考慮全域性或死鎖的問題。改進中利用了雜湊鍊錶最大的特點, 增刪操作時,都先直接先鎖表, 減少了不必要的死鎖處理。

三、讀寫鎖

有時候互斥量是不必要的, 例如讀乙個煉表裡的資訊時, 沒有必要鎖鏈表,直接查詢並返回即可,不會有衝突,

但是要修改鍊錶時,必須要鎖住。

這時候引入了讀寫鎖。

當只有讀鎖或者沒有鎖時,可以繼續加鎖讀,鎖數量+1

當沒有鎖時,可以加鎖寫。

#include #include //執行緒

//儲存了執行緒結構體j_id,以及乙個雙向指標

struct job;

//執行緒雙向鏈式佇列

//只能在隊頭和隊尾進行插入,但可以在任意位置刪除。

struct queue;

int queue_init(struct queue *qp)

void job_insert(struct queue *qp, struct job *jp)

void job_remove(struct queue *qp, struct job *jp)

else

jp->j_next->j_prev = jp->j_prev;

}else if(jp == qp->q_tail)

else

pthread_rwlock_unlock(&qp->q_lock);

}//查詢當前佇列中對應id的執行緒,並返回

struct job *job_find(struct queue *qp, pthread_t id)

顯然執行時, 每當create乙個執行緒,就給佇列加乙個,把執行緒的資訊放入。

然後經常會需要查詢執行緒,這時候用讀鎖即可,提高效率。

Linux 多執行緒互斥量互斥

同乙個程序中的多個執行緒共享所在程序的記憶體資源,當多個執行緒在同一時刻同時訪問同一種共享資源時,需要相互協調,以避免出現資料的不一致和覆蓋等問題,執行緒之間的協調和通訊的就叫做執行緒的同步問題,執行緒同步的思路 讓多個執行緒依次訪問共享資源,而不是並行 mutex被建立時可以有初始值,表示mute...

Linux 執行緒同步 互斥量(互斥鎖)

1 執行緒同步的目的是不管執行緒之間的執行如何穿插,其執行結果都是正確的。即保證多執行緒執行下結果的確定性。2 同步就是讓所有執行緒按照一定的規則執行,使得其正確性和效率都有跡可循,即執行緒同步就是對執行緒之間的穿插進行控制。3 每個物件都對應於乙個 互斥鎖 的標記,這個標記用來保證在任一時刻,只能...

執行緒同步 互斥量

下面以乙個簡單的多執行緒程式來演示如何使用互斥量來進行執行緒同步。在主線程中,我們建立子執行緒,並把陣列msg作為引數傳遞給子執行緒,然後主線程呼叫函式pthread mutex lock對互斥量加鎖,等待輸入,輸入完成後,呼叫函式pthread mutex unlock對互斥量解鎖,從而使執行緒函...