1. 程式人生 > >linux多執行緒中的共享變數

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語句就根本不需要放入臨界區.