執行緒安全與可重入性

2022-09-12 12:18:12 字數 3379 閱讀 5595

一組併發執行緒執行在同一程序上下文中,每個執行緒都有自己獨立的執行緒上下文,包括執行緒id、棧、棧指標、程式計數器(pc)、條件碼和通用目的暫存器值。每個執行緒和其它執行緒一起共享程序上下文的其他部分,包括整個使用者虛擬位址空間(由**段、讀/寫資料、堆以及所有共享庫的**和資料區組成)。執行緒也共享開啟的檔案集合。

當存在共享資源的時候,對資源的訪問需要同步。這時候使用執行緒編寫程式的時候,需要編寫具有執行緒安全性(thread safety)屬性的函式。乙個函式,當且僅當被多個併發執行緒反覆呼叫時,能夠一直產生正確的結果,才能夠被稱為執行緒安全的(thread-safe),否則我們稱其為非執行緒安全的(thread-unsafe)。

對共享變數的併發訪問會造成競爭,請看下面的例子:

// badcounter.c

#include #include #include #include "pv.h"

void *thread(void *vargp);

volatile int counter = 0;

int main(int argc, char **ar**)

niters = atoi(ar**[1]);

pthread_create(&tid1, null, thread, &niters);

pthread_create(&tid2, null, thread, &niters);

pthread_join(tid1, null);

pthread_join(tid2, null);

if(counter != 2 * niters)

fprintf(stderr, "boom! counter = %d\n", counter);

else

fprintf(stdout, "ok! counter = %d\n", counter);

exit(0);

}void *thread(void *vargp)

執行一下這個例子:

$ gcc -pthread badcounter.c -o badcounter

$ ./badcounter 100000000

boom! cnt = 177953661

$ ./badcounter 100000000

boom! cnt = 185993828

$ ./badcounter 100000000

boom! cnt = 176414058

如果我們每次執行這個例子,結果可能都不一致,原因是:對counter的訪問存在競爭。

解決這類問題的方法是:利用像p和v操作這樣的同步操作來保護共享的變數。這樣做有好處也有壞處:

乙個偽隨機數生成器設這類執行緒不安全函式的簡單例子:

unsigned int next = 1;

/* rand - 返回乙個在[0, 32767]範圍內的偽隨機數*/

int rand()

/* srand - 為rand函式設定隨機種子*/

void srand(unsigned int seed)

rand函式是執行緒不安全的,因為呼叫當前呼叫的結果依賴於前次呼叫的中間結果。當呼叫srandrand設定乙個種子後,我們從乙個單執行緒中反覆呼叫rand,能夠預期得到乙個可重複的隨機數字序列。然而在多執行緒呼叫rand函式,這種假設就不再成立了。

解決這一類問題唯一的方法是重寫它,使得它不再使用任何static資料,而是依靠呼叫者在引數中傳遞狀態資訊。這樣做的缺點很明顯:現在需要被迫修改呼叫rand的**。在乙個大的程式中,可能有成百上千不同的呼叫位置,做這樣的修改將是非常麻煩的,而且容易出錯。

一些函式,如ctime和gethostbyname,將計算結果存放在乙個static變數中,然後返回乙個指向這種變數的指標。如果我們從併發執行緒中呼叫這些函式,那麼將可能發生災難,因為正在被乙個執行緒使用的結果會被另乙個執行緒悄悄覆蓋了。

有兩種方法來處理這類執行緒不安全函式:

下面利用加鎖-拷貝技術,給出ctime的乙個執行緒安全的版本:

char *ctime_ts(const time_t *timep, char *privatep)

如果函式f呼叫執行緒不安全函式g,那麼f不一定是執行緒不安全的。

以下是一些執行緒不安全函式,以及它們的對應的執行緒安全版本。在編寫併發執行緒程式的時候,盡可能使用執行緒安全版本的函式。

有一類重要的執行緒安全函式,叫做可重入函式(reentrant function),其特點在於它們具有這樣一種屬性:當它們被多個執行緒呼叫時,不會引用任何共享資料。所有函式的集合被劃分成不相交的執行緒安全和執行緒不安全函式集合。可重入函式集合是執行緒安全函式集合的乙個真子集。它們的關係如下圖所示。

可重入函式通常要比不可重入函式的執行緒安全的函式高效一些,因為它們不需要同步操作!更進一步說,執行緒不安全函式rand轉化為可重入函式,就不要引用外部的共享變數,我們可以用乙個呼叫者傳遞進來的指標取代靜態的next變數:

/* rand_r 是乙個可重入的偽隨機數生成函式*/

int rand_r(unsigned int *nextp)

上面的rand_r是可重入的嗎?不一定。上面說過了,可重入函式不引用任何共享變數,但是我們不能夠保證呼叫者傳遞進來的nextp不是指向乙個共享變數(比如全域性變數或靜態變數)!

實際上,我們所說的可重入函式包含兩類:顯式可重入函式(explicitly reentrant function)和隱式可重入函式(implicitly reentrant function)

如果所有的函式引數都是傳值傳遞的(即沒有指標),並且所有的資料引用都是本地的自動棧變數(即沒有引用全域性或靜態變數),那麼函式就是顯示可重入的。也就是說,無論它是被如何呼叫的,我們都可以斷言它是可重入的。

如果允許顯式可重入函式中的一些引數是引用傳遞的(即允許傳遞指標),那麼就得到乙個隱式可重入函式。也就是說,如果呼叫執行緒小心地傳遞指向非共享資料的指標,那麼它是可重入的。例如上面的rand_r就是隱式可重入的。

通過上面的分析,有乙個點值得注意:

可重入與執行緒安全

之前一直糾結可重入與執行緒安全的區別,今天詳細查了一下。其實根據兩個概念的名字就可以得出結論,可重入就是重複多次結果都是一樣的,而執行緒安全則不一樣,只要不同執行緒執行的時候不會出現因不同執行緒執行順序不同而結果不同就可以。大多數情況下,要將不可重入函式改為可重入的,需要修改函式介面,使得所有的資料...

執行緒安全與可重入

1 什麼是執行緒安全 當乙個函式被多個執行緒反覆呼叫的時候,他會一直產生正確的結果,那麼這個函式就是執行緒安全的。執行緒安全函式解決了多個執行緒呼叫函式時訪問臨界資源的衝突問題。2 可重入 在多執行緒或有異常控制流的情況下,當某個函式執行到中途時,控制流有可能被打斷去執行另乙個函式,而這 另乙個函式...

可重入與執行緒安全

概念 可重入性class counter void increment void decrement intvalue const private int n counter類是可重入,但不是執行緒安全的。因為 和 操作符不是原子性的,它們通常要經歷以下三個步驟 暫存器讀取記憶體中變數的值 增加或減...