Linux - 線程同步

條件變量

使用互斥鎖可以解決線程安全的問題,保證多線程下臨界資源數據的正確性.
但是僅僅互斥還是會存在一些問題.
  • 某個線程獲取鎖之後, 發現數據沒有就緒, 又立刻釋放鎖.
  • 如果這個線程的優先級很高, 那麼就可能在釋放了鎖之後又立刻嘗試獲取鎖, 再立刻釋放.
  • 依次類推. 這樣雖然並沒有發生死鎖, 但是這個線程空轉又佔用了鎖資源, 導致其他線程很難獲取到這個鎖.

條件變量:
  • 當一個線程互斥的訪問某個變量時,在某一線程的狀態沒有發生改變之前,他什麼也是做不了.
以打球舉例:A負責傳球,B負責投籃,當B想要投籃的時候哦,但是發現A並沒就將球傳給自己之前,就不能去投籃,只能等待A將球傳給自己纔可以繼續執行自己的投籃任務

條件變量函數

初始化
int pthread_cond_init(pthread_cond_t *restrict cond,const pthread_condattr_t *restrict attr); 

參數: 
    cond:要初始化的條件變量 
    attr:NULL

銷燬
int pthread_cond_destroy(pthread_cond_t *cond);

等待條件滿足
int pthread_cond_wait(pthread_cond_t *restrict cond,pthread_mutex_t *restrict mutex); 

參數:    
    cond:要在這個條件變量上等待 
    mutex:互斥量

喚醒等待
int pthread_cond_broadcast(pthread_cond_t *cond); 
int pthread_cond_signal(pthread_cond_t *cond);

代碼舉例:(以打球爲例)
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>

pthread_cond_t g_shot;
pthread_mutex_t g_lock;

void* Pass(void* arg)
{
    (void)arg;
    while(1)
    {
        printf("1 : 傳球\n");
        usleep(789123);
        pthread_cond_signal(&g_shot);
    }
    return NULL;
}

void* Shot(void* arg)
{
    (void)arg;
    while(1)
    {
        pthread_cond_wait(&g_shot,&g_lock);
        printf("2 : 投籃\n");
        usleep(123456);
    }
    return NULL;
}

int main()
{
    pthread_mutex_init(&g_lock,NULL);
    pthread_cond_init(&g_shot,NULL);

    //1.球員1負責傳球
    //2.球員2負責投籃
    pthread_t t1,t2;
    pthread_create(&t1,NULL,Pass,NULL);
    pthread_create(&t2,NULL,Shot,NULL);
    pthread_join(t1,NULL);
    pthread_join(t2,NULL);

    pthread_mutex_destroy(&g_lock);
    pthread_cond_destroy(&g_shot);
    return 0;
}

未加條件變量的運行結果:

因爲我們設置的是傳球比較慢一些,投籃比較快一些,可以看到,在"投籃"一直在運行,消耗着資源

加了條件變量的運行結果:
(忽略剛開始的不穩定情況)
我們可以看到,傳球和投籃一定是交替運行的,在傳球結束以後會發送一個信號,"投籃"發現條件滿足了就會開始投籃,在這之前一直等待

爲什麼 pthread_cond_wait 需要互斥量?
條件變量是爲了解決某些情況下互斥鎖低效的問題. 因此對條件變量的操作, 必然要和互斥鎖密切相關. 
  • 條件等待是線程間同步的一種手段,如果只有一個線程,條件不滿足,一直等下去都不會滿足,所以必須要有一個線程通過某些操作,改變共享變量,使原先不滿足的條件變得滿足,並且友好的通知等待在條件變量上的線程。
  • 條件不會無緣無故的突然變得滿足了,必然會牽扯到共享數據的變化。所以一定要用互斥鎖來保護。沒有互斥鎖就無法安全的獲取和修改共享數據。

觀察一下的代碼看看有什麼問題?
pthread_mutex_lock(&mutex);
while(條件爲假){
    pthread_mutex_unlock(&mutex);
    ////////////////////////////////////////////////////////////
    // 在這一部分可能會出現問題!!!!
    ////////////////////////////////////////////////////////////
    pthread_cond_wait(&cond);
    pthread_mutex_lock(&mutex);
}
pthread_mutex_unlock(&mutex);

我們分析以上的代碼,可能會出現如下問題:

  • 由於解鎖unlock並不是原子操作,所以在線程1調用解鎖以後,wait之前,如果線程2獲得了互斥量,摒棄了條件滿足,發送了信號,那麼線程1的wait將錯過這個信號,可能導致線程永遠阻塞在wait這裏.所以,解鎖和等待必須是一個原子操作
  • wait函數中的互斥量進入函數以後,回去查看當前的條件是不是爲假,如果是假,就將互斥量置爲1,知道 wait 返回,把條件改爲1,將互斥量置爲原來的樣子,保證wait不錯過信號量,避免阻塞.

條件變量使用規範

  • 等待條件代碼
pthread_mutex_lock(&mutex);
while(條件爲假)
    pthread_cond_wait(cond,mutex);
修改條件
pthread_mutex_unlock(&mutex);

  •  給條件發送信號代碼
pthread_mutex_lock(&mutex);
設置條件爲真
pthread_cond_signal(cond);
pthread_mutex_unlock(&mutex);

發佈了77 篇原創文章 · 獲贊 50 · 訪問量 2萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章