linux多執行緒中的共享變數
當解決多執行緒互斥同步的問題時,經常會有如下幾個問題:
1. 在一個給定的問題中,需要多少個Mutex,多少個Semaphore?有什麼規律?
2. 在對臨界區加鎖和等待訊號量的順序上有什麼要求和規律?
3. 什麼樣操作適合放在臨界區,什麼樣的不適合?
下面就生產者和消費者問題來分析一些這幾個問題.
下面是一個簡單的實現程式:
生產者向陣列sharedArray中寫入資料,而消費者從該陣列中讀取資料.
#include <stdio.h>
#include <pthread.h>
#include <semaphore.h>
#include <stdlib.h>
#define MAXSIZE 5 /*共享緩衝區的大小*/
int sharedArray[MAXSIZE]; /*sharedArray是共享緩衝區*/
int curr=-1; /*curr是用來指定sharedArray當前存有資料的最大位置*/
/*注意,sharedArray和curr都屬於共享資料*/
int empty=0;
int full=MAXSIZE;
pthread_mutex_t sharedMutex=PTHREAD_MUTEX_INITIALIZER; /*鎖定臨界區的mutex*/
sem_t waitNonEmpty, waitNonFull; /*等待"非空資源"和等待"非滿資源"的semaphor*/
void * readData(void * whichone)
{
int data, position;
while (1){
sem_wait(&waitNonEmpty); /*是否有"非空資源"*/
pthread_mutex_lock(&sharedMutex); /*進入臨界區*/
data = sharedArray[curr];
position = curr--;
printf ("%s read from the %dth: %d, /n", (char*)whichone, position, data);
sem_post(&waitNonFull); /*生成一個"非滿資源"*/
pthread_mutex_unlock(&sharedMutex); /*離開臨界區*/
sleep(2); /*跟同步無關的費時操作*/
}
}
void * writeData(void * whichone)
{
int data, position;
while (1) {
data=(int)(10.0*random()/RAND_MAX); /*生成一個隨機資料,注意是10.0而不是10*/
sem_wait(&waitNonFull); /*是否有"非滿資源"*/
pthread_mutex_lock(&sharedMutex); /*進入臨界區*/
position = ++curr;
sharedArray[curr]=data;
printf ("%s wrote to the %dth: %d, /n", (char*)whichone, position, data);
sem_post(&waitNonEmpty); /*生成一個"非空資源"*/
pthread_mutex_unlock(&sharedMutex); /*離開臨界區*/
sleep(1); /*跟同步無關的費時操作*/
}
}
int main (int argc, char** argv)
{
pthread_t consumer1, consumer2, producer1, producer2; /*兩個生產者和兩個消費者*/
sem_init(&waitNonEmpty, 0, empty); /*初始化訊號量*/
sem_init(&waitNonFull, 0, full);
/*注意,本問題中的兩種semaphore是有一定關係的,那就是它們的初始值之和應該等於共享緩衝區大小*/
/*即empty+full等於MAXSIZE*/
pthread_create (&consumer1, NULL, &readData, "consumer1");
pthread_create (&consumer2, NULL, &readData, "consumer2");
pthread_create (&producer1, NULL, &writeData, "producer1");
pthread_create (&producer2, NULL, &writeData, "producer2");
pthread_join (consumer1, NULL);
pthread_join (consumer2, NULL);
pthread_join (producer1, NULL);
pthread_join (producer2, NULL);
sem_destroy(&waitNonEmpty);
sem_destroy(&waitNonFull);
}
分析和說明:
1. 在一個給定的問題中,需要多少個Mutex,多少個Semaphore?有什麼規律?
在本問題中,共需要一個Mutex和兩個Semaphore.
其中,Mutex是用來鎖定臨界區的,以解決對共享資料的互斥訪問問題(無論是對生成者還是對消費者);
我們共需要兩個Semaphore,這是因為在本問題中共有兩個稀缺資源.
第一種是"非空"這種資源,是在消費者之間進行競爭的.
第二種是"非滿"這種資源,是在生產者之間進行競爭的.
所以,一般來說,需要鎖定臨界區,就需要Mutex;有幾種稀缺資源就需要幾個Semaphore.
對稀缺資源的分析不能想當然.稀缺資源不一定是指被共享的資源,很多時候是指執行緒會被阻塞的條件(除了要進臨界區被阻塞外).
本例中,消費者會在緩衝區為空時被阻塞,所以"非空"是一種稀缺資源;
生產者會在緩衝區為滿時被阻塞,所以"非滿"也是一種稀缺資源.
2. 在對臨界區加鎖和等待訊號量的順序上有什麼要求和規律?
這裡要說兩點:
第一,不要將等待訊號量的語句放在被鎖定的臨界區內,這樣會造成死鎖.而且這也是很沒有必要的.
比如,消費者在緩衝區沒有資料的時候進入臨界區,這樣就會把臨界區鎖上,由於沒有資料,消費者也會被鎖上.
這時,任何生產者都會由於臨界區被鎖上而被block住,這樣就造成了死鎖.
第二,如果有多個Semaphore需要等待,那麼每個執行緒中,最好對這多個訊號量進行等待的順序一致,
不然的話很容易造成死鎖.
3. 什麼樣操作適合放在臨界區,什麼樣的不適合?
一般來說,臨界區中只放對共享資料進行訪問的語句,這樣會改善程式的效能.
很多時候,取出共享資料的副本後,對副本進行費時的各種操作就不需要放在臨界區了.
比如,本例中的sleep語句就根本不需要放入臨界區.