mutex用於多線程或者多進程之間對臨界資源的訪問互斥,在具體應用中,一般傾向於,一遇到資源競爭情況,就考慮用mutex,可是,mutex會大大降低程式的並發。因此,應該謹慎:
第一,避免用mutex,例如可以改用單線程程式
第二,迫不得已,儘可能使mutex的臨界區短小,也就是說,儘可能晚lock,早unlock,減小鎖的粒度
第三,可以考慮讀寫鎖
本文來自:http://hi.baidu.com/yenoeepfqabiyzq/item/f2e46cdd276f74cb1b72b447
轉自:http://hi.baidu.com/yenoeepfqabiyzq/item/5d0cc4d65f9d8357d73aae34
編寫可重新進入和安全執行緒的代碼在單線程的進程中,有且僅有一個控制流程。這種進程執行的代碼不必是可重新進入的,或安全執行緒的。在多線程的程式中,同一個函數或是同一個資源可能被多個控制流程並發地訪問。為了保證資源的完整性,為多線程程式寫的代碼必須是可重新進入的和安全執行緒的。本節提供了一些編寫可重新進入和安全執行緒的代碼的資訊。理解可重新進入和安全執行緒可重新進入和安全執行緒都是指函數處理資源的方式。可重新進入和安全執行緒是兩個相互獨立的概念,一個函數可以僅是可重新進入的,可以僅是安全執行緒的,可以兩者都是,可以兩者都不是。可重新進入一個可重新進入的函數不能為後續的調用保持靜態(或全域)資料,也不能返回指向靜態(或全域)資料的指標。函數中用到的所有的資料,都應該由該函數的調用者提供(不包括棧上的局部資料)。一個可重新進入的函數不能調用不可重新進入的函數。一個不可重新進入的函數經常可以(但不總是可以)通過它的外部介面和功能識別出來。例如strtok是不可重新進入的,因為它儲存著將被識別為token的字串。ctime也不是一個可重新進入的函數,它會返回一個指向待用資料的指標,每次調用都可能覆蓋這些資料。
安全執行緒一個安全執行緒的函數通過“鎖”來保護共用的資源不被並發地訪問。“安全執行緒”僅關心函數的實現,而不影響它的外部介面。 在C中,局部變數是在棧上動態分配的,因此,任何一個不使用待用資料和其它共用資源的函數就是最平凡的安全執行緒的。例如,下面這個函數就是安全執行緒的:
/* thread-safe function */int diff(int x, int y) {int delta;delta = y - x;if (delta < 0)delta = -delta;return delta;}對全域變數的使用是線程“不安全”的。應該為每一個線程維護一份拷貝,或者將其封裝起來,使得對它的訪問變成串列的。 使一個函數變成可重新進入的在大部分情況下,不可重新進入的函數修改成可重新進入的函數時,需要修改函數的對外介面。不可重新進入的函數不能被多線程的程式調用。一個不可重新進入的函數,基本上不可能是安全執行緒的。 傳回值很多不可重新進入的函數返回一個指向待用資料的指標。這個問題可以有兩種解決辦法:
1、返回從堆中分配的空間的地址。在這種情況下,調用者必須負責釋放堆中的空間。這種辦法的優點是不必修改函數的外部介面,但是不能向後相容。現存的單線程的程式使用修改後的函數會導致記憶體泄露(因為它們沒有釋放空間)。 2、由調用者提供空間。儘管函數的外部介面需要改變,仍然推薦這種方法。 例如,一個strtoupper函數(個人感覺這個東東寫得很儍)一個字串轉換成大寫,實現為:
/* non-reentrant function */char *srttoupper(char *string) {static char buffer[MAX_STR_SIZE];int index;for (index = 0; string[index]; ++index) {buffer[index] = toupper(string[index]);}buffer[index] = 0;return buffer;}該函數既不是可重新進入的,也不是安全執行緒的。使用第一種方法將其改寫為可重新進入的:/* reentrant function (a poor solution)*/char *strtoupper(char *string) {
char *buffer;
int index;
buffer = malloc(MAX_STR_SIZE);/*error check should be checked*/
for (index = 0; string[index]; ++index) {
buffer[index] = toupper(string[index]);
}
buffer[index] = 0;
return buffer;
}使用第二種方法解決:/* reentrant function (a better solution)*/char *strtoupper_r(char *in_str, char *out_string) {
int index;
for (index = 0; in_str[index]; ++index) {
out_str[index] = toupper(in_str[index]);
}
out_str[index] = 0;
return out_str;
}為後繼的調用保持資料一個可重新進入的函數不應該為後續的調用保持資料(即後繼的調用和本次調用無關),因為下一次調用可能是由不同的線程調用的。如果一個函數需要在連續的調用之間維護一些資料,例如一個工作緩衝區或是一個指標,這些資料(資源)應該由調用這個函數的函數提供。例如:返回指定字串中的下一個小寫字元的函數。字串只有在第一次調用時被提供,就像strtok一樣。到達末尾時返回0(我認為這個函數寫得有問題,姑且明白大意就好):/* non-reentrant function */char lowercase_c(char *string) {
static char *buffer;
static int index;
char c = 0;
if (string != NULL) {
buffer = string;
index = 0;
}
for ( ; c = buffer[index]; ++index) {
if (islower(c)) {
index++;
break;
}
}
return c;
}該函數是不可重新進入的。要使它改寫成可重新進入的,其中的待用資料應該由它的調用者維護。/* reentrant function */char reentrant_lowercase_c(char *string, int *p_index) {
char c = 0;
for ( ; c = string[*p_index]; ++(*p_index)) {
if (islower(c)) {
(*p_index)++;
break;
}
}
return c;
}函數的對外介面和使用方法均改變了。 使一個函數變成安全執行緒的在一個多線程的程式中,所有的被多線程調用的函數多必須是安全執行緒的(或可重新進入的)。注意,不可重新進入的函數一般都是線程“不安全”的,然而,將它們改寫成可重新進入的同時,一般就會將它們變成安全執行緒的。“鎖”住共用資源使用待用資料或者其它任何共用資源(如檔案、終端等)的函數,必須對這些資源加“鎖”以實現對它們的串列訪問,這樣才能成為安全執行緒的函數。例如: /* thread-unsafe function */int increament_counter() {
static int counter = 0;
counter++;
return counter;
}/* pseudo-code thread-safe function*/int increment_counter() {
static int counter = 0;
static lock_type counter_lock = LOCK_INITIALIZER;
lock(counter_lock);
counter++;
unlock(counter_lock);
return counter;
}在一個使用線程庫的多線程程式中,應該使用訊號量來序列化共用資源的訪問,或者其它“鎖” 後面還有兩節:A Workaround for Thread-Unsafe FunctionsReentrant and Thread-Safe Libraries與本文主題關係不大... 綜上所述:可重新進入:多個線程調用是不會互相影響。例如第一個lowercase_c函數是不可重新進入的,在另一個線程中第二次調用它時,必將覆蓋掉第一個線程中的設定的字串。安全執行緒:解決多個線程共用資源的問題。