Linux線程同步與互斥

來源:互聯網
上載者:User

● 互斥鎖

互斥鎖用來保證同一時間內只有一個線程在執行某段代碼(臨界區)。多線程編程最容易出問題的地方,就是臨界區的界定和訪問
控制。下面是一個生產者,消費者的簡單例子。生產者、消費者公用一個緩衝區,這裡假定緩衝區只能存放一條訊息。

#include <stdio.h>#include <stdlib.h>#include <pthread.h>#include <sys/time.h>static char buff[50];int have_msg=0;pthread_mutex_t mutex;int delay=1;void consumeItem(char *buff){    printf("consumer item\n");}void produceItem(char *buff){    printf("produce item\n");}void *consumer(void *param){    while (1)    {        pthread_mutex_lock(&mutex);        if (have_msg>0)        {            have_msg--;            consumeItem(buff);        }        pthread_mutex_unlock(&mutex);        sleep(delay);    }    return NULL;}void *producer(void *param){    while (1)    {        pthread_mutex_lock(&mutex);        if (have_msg==0)        {            have_msg++;            produceItem(buff);        }        pthread_mutex_unlock(&mutex);        sleep(delay);    }    return NULL;}int main(){    pthread_t tid_c, tid_p;    void *retval;    pthread_mutex_init(&mutex, NULL);    pthread_create(&tid_p, NULL, producer, NULL);    pthread_create(&tid_c, NULL, consumer, NULL);    pthread_join(tid_p, &retval);    pthread_join(tid_c, &retval);    return 0;}

輸出一定是這樣的:

互斥鎖最簡單的使用是這樣的:
pthread_mutex_t mutex;                   //定義鎖
pthread_mutex_init(&mutex, NULL);   //預設屬性初始化鎖
pthread_mutex_lock(&mutex);           //申請鎖
...
pthread_mutex_unlock(&mutex);       //釋放鎖
設定鎖的屬性
函數pthread_mutexattr_setpshared和函數pthread_mutexattr_settype用來設定互斥鎖屬性。
前一個函數設定屬性pshared,它有兩個取值,PTHREAD_PROCESS_PRIVATE和PTHREAD_PROCESS_SHARED。前者用來不同進程中的線程同步,後者用於同步本進程的不同線程。在上面的例子中,我們使用的是預設屬性PTHREAD_PROCESS_ PRIVATE。後者用來設定互斥鎖類型,可選的類型有PTHREAD_MUTEX_NORMAL、PTHREAD_MUTEX_ERRORCHECK、PTHREAD_MUTEX_RECURSIVE和PTHREAD _MUTEX_DEFAULT。它們分別定義了不同的上所、解鎖機制,一般情況下,選用最後一個預設屬性。用法如下:

pthread_mutexattr_t mutexAttr;pthread_mutexattr_init(&mutexAttr);pthread_mutexattr_setpshared(&mutexAttr, PTHREAD_PROCESS_PRIVATE);pthread_mutexattr_settype(&mutexAttr, PTHREAD_MUTEX_DEFAULT);pthread_mutex_init(&mutex, &mutexAttr);

pthread_mutex_lock阻塞線程直到pthread_mutex_unlock被調用。
還有另外兩個函數可以用:int pthread_mutex_trylock (pthread_mutex_t *__mutex), 該函數立即返回,根據返回狀態判斷加鎖是否成功。int pthread_mutex_timedlock (pthread_mutex_t *mutex, struct timespec *__restrict),該函數逾時返回。
互斥鎖主要用來互斥訪問臨界區。用於線程的互斥。

● 條件變數

條件變數用來阻塞線程等待某個事件的發生,並且當等待的事件發生時,阻塞線程會被通知。
互斥鎖一個明顯的缺點是它只有兩種狀態:鎖定和非鎖定。而條件變數通過允許線程阻塞和等待另一個線程發送訊號的方法彌補了互斥鎖的不足,它常和互斥鎖一起使用。使用時,條件變數被用來阻塞一個線程,當條件不滿足時,線程往往解開相應的互斥鎖並等待條件發生變化。一旦其它的某個線程改變了條件變數,它將通知相應的條件變數喚醒一個或多個正被此條件變數阻塞的線程。這些線程將重新鎖定互斥鎖並重新測試條件是否滿足。一般說來,條件變數被用來進行線承間的同步。
條件變數的結構為pthread_cond_t
最簡單的使用例子:

pthread_cond_t cond;pthread_cond_init(&cond, NULL);pthread_cond_wait(&cond, &mutex);    ...pthread_cond_signal(&cond);

調用pthread_cond_wait的線程會按調用順序進入等待隊列,當有一個訊號產生時,先進入阻塞隊列的線程先得到喚醒。條件變數在某些方面看起來差不多。
正如上面所說的,條件變數彌補了互斥鎖的不足。
接著上面列舉的生產者、消費者例子中,我們這裡增加消費者(並假設緩衝區可以放任意多條資訊),比如有3個消費者線程,如果都使用互斥鎖,那麼三個線程都要不斷的去查看緩衝區是否有訊息,有就取走。這無疑是資源的極大浪費。如果我們用條件變數,三個消費者線程都可以放心的“睡覺”,緩衝區有訊息,消費者會收到通知,那時起來收訊息就好了。

#include <stdio.h>#include <stdlib.h>#include <pthread.h>#include <sys/time.h>static char buff[50];pthread_mutex_t mutex;pthread_mutex_t cond_mutex;pthread_cond_t cond;void consumeItem(char *buff){    printf("consumer item\n");}void produceItem(char *buff){    printf("produce item\n");}void *consumer(void *param){    int t = *(int *)param;    while (1)    {        pthread_cond_wait(&cond, &cond_mutex);        pthread_mutex_lock(&mutex);        printf("%d: ", t);        consumeItem(buff);        pthread_mutex_unlock(&mutex);    }    return NULL;}void *producer(void *param){    while (1)    {        pthread_mutex_lock(&mutex);        produceItem(buff);                pthread_mutex_unlock(&mutex);        pthread_cond_signal(&cond);        sleep(1);    }    return NULL;}int main(){    pthread_t tid_c, tid_p, tid_c2, tid_c3;    void *retval;    pthread_mutex_init(&mutex, NULL);    pthread_mutex_init(&cond_mutex, NULL);        pthread_cond_t cond;    pthread_cond_init(&cond, NULL);            int p[3] = {1, 2, 3}; //用來區分是哪個線程    pthread_create(&tid_p, NULL, producer, NULL);    pthread_create(&tid_c, NULL, consumer, &p[0]);    pthread_create(&tid_c2, NULL, consumer, &p[1]);    pthread_create(&tid_c3, NULL, consumer, &p[2]);        pthread_join(tid_p, &retval);    pthread_join(tid_c, &retval);    pthread_join(tid_c2, &retval);    pthread_join(tid_c3, &retval);            return 0;}

要注意的是,條件變數只是起阻塞和喚醒線程的作用,具體的判斷條件還需使用者給出,例如緩衝區到底有多少條資訊等。
與條件變數有關的其它函數有:
/* 銷毀條件變數cond */
int pthread_cond_destroy (pthread_cond_t *cond);  
/* 喚醒所有等待條件變數cond的線程 */
int pthread_cond_broadcast (pthread_cond_t *cond);

/* 等待條件變數直到逾時 */
int pthread_cond_timedwait (pthread_cond_t *cond,
pthread_mutex_t *mutex,
const struct timespec *abstime);
/* 初始化條件變數屬性 attr*/
int pthread_condattr_init (pthread_condattr_t *attr);
/* 銷毀條件變數屬性 */
int pthread_condattr_destroy (pthread_condattr_t *attr)

/* 讀取條件變數屬性attr的進程共用標誌 */
int pthread_condattr_getpshared (const pthread_condattr_t *
attr,
int *pshared);

/* 更新條件變數屬性attr的進程共用標誌 */
int pthread_condattr_setpshared (pthread_condattr_t *attr,
int pshared);

一般來說,條件變數被用來進行線程間的同步。

● 訊號量

訊號量本質上是一個非負的整數計數器,它被用來控制對公用資源的訪問。當公用資源增加時,調用函數sem_post()增加訊號量。
只有當訊號量值大於0時,才能使用公用資源,使用後,函數sem_wait()減少訊號量。函數sem_trywait()和函數
pthread_ mutex_trylock()起同樣的作用,它是函數sem_wait()的非阻塞版本。

下面我們逐個介紹和訊號量有關的一些函數,它們都在標頭檔/usr/include/semaphore.h中定義。
  訊號量的資料類型為結構sem_t,它本質上是一個長整型的數。函數sem_init()用來初始化一個訊號量。它的原型為:
  extern int sem_init __P ((sem_t *__sem, int __pshared, unsigned int __value));
  sem為指向訊號量結構的一個指標;pshared不為0時此訊號量在進程間共用,否則只能為當前進程的所有線程共用;value給出了訊號量的初始值。
  函數sem_post( sem_t *sem )用來增加訊號量的值。當有線程阻塞在這個訊號量上時,調用這個函數會使其中的一個線程不在阻塞,選擇機制同樣是由線程的調度策略決定的。
  函數sem_wait( sem_t *sem )被用來阻塞當前線程直到訊號量sem的值大於0,解除阻塞後將sem的值減一,表明公用資源經使用後減少。函數sem_trywait ( sem_t *sem )是函數sem_wait()的非阻塞版本,它直接將訊號量sem的值減一。
  函數sem_destroy(sem_t *sem)用來釋放訊號量sem。

在上面的生產者、消費者例子中,我們假設緩衝區最多能放10條訊息。用訊號量來實現如下:

#include <stdio.h>#include <stdlib.h>#include <pthread.h>#include <semaphore.h>static char buff[50];pthread_mutex_t mutex;pthread_mutex_t cond_mutex;pthread_cond_t cond;sem_t msg_cnt;      //緩衝區訊息數sem_t space_cnt;   //緩衝區空閑數void consumeItem(char *buff){    printf("consumer item\n");}void produceItem(char *buff){    printf("produce item\n");}void *consumer(void *param){    while (1)    {        sem_wait(&msg_cnt);        pthread_mutex_lock(&mutex);        consumeItem(buff);        pthread_mutex_unlock(&mutex);        sem_post(&space_cnt);    }    return NULL;}void *producer(void *param){    while (1)    {        sem_wait(&space_cnt);        pthread_mutex_lock(&mutex);        produceItem(buff);                pthread_mutex_unlock(&mutex);        sem_post(&msg_cnt);            }    return NULL;}int main(){    pthread_t tid_c, tid_p;    void *retval;    pthread_mutex_init(&mutex, NULL);    pthread_mutex_init(&cond_mutex, NULL);        pthread_cond_t cond;    pthread_cond_init(&cond, NULL);    sem_init(&msg_cnt, 0, 0);        //初始緩衝區沒有訊息    sem_init(&space_cnt, 0, 10);  //初始緩衝區能放10條訊息        pthread_create(&tid_p, NULL, producer, NULL);    pthread_create(&tid_c, NULL, consumer, NULL);                pthread_join(tid_p, &retval);    pthread_join(tid_c, &retval);                return 0;}

 

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.