線程同步的三種方式

互斥鎖

互斥鎖用來保證一段時間內只有一個線程在執行一段代碼。

我們先看下面一段代碼。這是一個讀/寫程序,它們公用一個緩沖區,并且我們假定一個緩沖區只能保存一條信息。即緩沖區只有兩個狀態:有信息或沒有信息。

void reader_function ( void );
void writer_function ( void ); 
char buffer;
int buffer_has_item=0;
pthread_mutex_t mutex;
struct timespec delay;

void main ( void ){
    pthread_t reader;
    /* 定義延遲時間*/
    delay.tv_sec = 2;
    delay.tv_nec = 0;
    /* 用默認屬性初始化一個互斥鎖對象*/
    pthread_mutex_init (&mutex,NULL);
    //創建線程任務
    pthread_create(&reader, pthread_attr_default, (void *)&reader_function), NULL);
    writer_function( );
}

void writer_function (void){
    while(1){
        /* 鎖定互斥鎖*/
        pthread_mutex_lock (&mutex);
        if (buffer_has_item==0){
            buffer=make_new_item( );
            buffer_has_item=1;
        }
        /* 打開互斥鎖*/
        pthread_mutex_unlock(&mutex);
        pthread_delay_np(&delay);
    }
}

void reader_function(void){
    while(1){
        pthread_mutex_lock(&mutex);
        if(buffer_has_item==1){
            consume_item(buffer);
            buffer_has_item=0;
        }
        pthread_mutex_unlock(&mutex);
        pthread_delay_np(&delay);
    }
}

這里聲明了互斥鎖變量mutex,結構pthread_mutex_t為不公開的數據類型,其中包含一個系統分配的屬性對象。

函數pthread_mutex_init用來生成一個互斥鎖。NULL參數表明使用默認屬性。
pthread_mutex_lock聲明開始用互斥鎖上鎖,此后的代碼直至調用pthread_mutex_unlock為止,均被上鎖,即同一時間只能被一個線程調用執行。當一個線程執行到pthread_mutex_lock處時,如果該鎖此時被另一個線程使用,那此線程被==阻塞==,即程序將等待到另一個線程釋放此互斥鎖。在上面的例子中,我們使用了==pthread_delay_np函數,讓線程睡眠一段時間==,就是為了防止一個線程始終占據此函數。

- 死鎖情況

需要提出的是在使用互斥鎖的過程中很有可能會出現死鎖:
兩個線程試圖同時占用兩個資源,并按不同的次序鎖定相應的互斥鎖。

例如兩個線程都需要鎖定互斥鎖1和互斥鎖2,a線程先鎖定互斥鎖1,b線程先鎖定互斥鎖2,這時就出現了死鎖。

此時我們可以使用函數pthread_mutex_trylock,它是函數pthread_mutex_lock的非阻塞版本,當它發現死鎖不可避免時,它會返回相應的信息,程序員可以針對死鎖做出相應的處理。另外不同的互斥鎖類型對死鎖的處理不一樣,但最主要的還是要程序員自己在程序設計注意這一點。

條件變量

我們講述了如何使用互斥鎖來實現線程間數據的共享和通信,互斥鎖一個明顯的缺點是它只有兩種狀態:==鎖定和非鎖定==。

條件變量通過允許線程阻塞和等待另一個線程發送信號的方法彌補了互斥鎖的不足,它常和互斥鎖一起使用。使用時,條件變量被用來阻塞一個線程,當條件不滿足時,線程往往解開相應的互斥鎖并等待條件發生變化。一旦其它的某個線程改變了條件變量,它將==通知相應的條件變量喚醒一個或多個正被此條件變量阻塞的線程==。這些線程將重新鎖定互斥鎖并重新測試條件是否滿足。一般說來,條件變量被用來進行線承間的同步。

條件變量的結構為pthread_cond_t

函數pthread_cond_init()被用來初始化一個條件變量。

原型為:

extern int pthread_cond_init __P ((pthread_cond_t *__cond,__const pthread_condattr_t *__cond_attr));

  • cond是一個指向結構pthread_cond_t的指針
  • cond_attr是一個指向結構pthread_condattr_t的指針
  • 結構pthread_condattr_t是條件變量的屬性結構,

注意初始化條件變量只有未被使用時才能重新初始化或被釋放。釋放一個條件變量的函數為pthread_cond_destroy(pthread_cond_t cond)

函數pthread_cond_wait() 使線程阻塞在一個條件變量上。
它的函數原型為:

extern int pthread_cond_wait __P ((pthread_cond_t *__cond,
pthread_mutex_t *__mutex));

線程解開mutex指向的鎖并被條件變量cond阻塞。線程可以被函數pthread_cond_signal和函數pthread_cond_broadcast喚醒,但是要注意的是,條件變量只是起阻塞和喚醒線程的作用,具體的判斷條件還需用戶給出,例如一個變量是否為0等等,這一點我們從后面的例子中可以看到。線程被喚醒后,它將重新檢查判斷條件是否滿足,如果還不滿足,一般說來線程應該仍阻塞在這里,被等待被下一次喚醒。這個過程一般用while語句實現。

另一個用來阻塞線程的函數是pthread_cond_timedwait(),它的原型為:

extern int pthread_cond_timedwait __P ((pthread_cond_t *__cond,
pthread_mutex_t *__mutex, __const struct timespec *__abstime));

它比函數pthread_cond_wait()多了一個時間參數,經歷abstime段時間后,即使條件變量不滿足,阻塞也被解除。

函數pthread_cond_signal()的原型為:

extern int pthread_cond_signal __P ((pthread_cond_t *__cond));

它用來釋放被阻塞在條件變量cond上的一個線程。多個線程阻塞在此條件變量上時,哪一個線程被喚醒是由線程的調度策略所決定的。要注意的是,必須用保護條件變量的互斥鎖來保護這個函數,否則條件滿足信號又可能在測試條件和調用pthread_cond_wait函數之間被發出,從而造成無限制的等待。下面是使用函數pthread_cond_wait()和函數

/* 使用boost中的條件變量和互斥鎖模仿信號量的一個簡單的例子。*/

class Semaphore {
public:
    Semaphore(long count = 0)
        : count_(count) {
    }

    void Signal() 
    {
        boost::unique_lock<boost::mutex> lock(mutex_);
        ++count_;
        cv_.notify_one();
    }

    void Wait() 
    {
        boost::unique_lock<boost::mutex> lock(mutex_);
        cv_.wait(lock, boost::bind(&Semaphore::is_full, this));
        --count_;
    }
    
    bool is_full()
    {
        return count_ > 0;
    }

private:
    boost::mutex mutex_;
    boost::condition_variable cv_;
    long count_;
};

count值為0時,Wait函數在Wait處被阻塞,并打開互斥鎖lock。此時,當調用到函數Signal時,notify_one()函數改變條件變量,告知Wait()停止阻塞。

函數pthread_cond_broadcast(pthread_cond_t *cond)用來喚醒所有被阻塞在條件變量cond上的線程。這些線程被喚醒后將再次競爭相應的互斥鎖,所以必須小心使用這個函數。

信號量

信號量本質上是一個非負的整數計數器,它被用來控制對公共資源的訪問。當公共資源增加時,調用函數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給出了信號量的初始值。
  1. 函數sem_post( sem_t *sem )

用來增加信號量的值。當有線程阻塞在這個信號量上時,調用這個函數會使其中的一個線程不在阻塞,選擇機制同樣是由線程的調度策略決定的。

  1. 函數sem_wait( sem_t *sem )

被用來阻塞當前線程直到信號量sem的值大于0,解除阻塞后將sem的值減一,表明公共資源經使用后減少。

  1. 函數sem_trywait ( sem_t *sem )

是函數sem_wait()的非阻塞版本,它直接將信號量sem的值減一。

  1. 函數sem_destroy(sem_t *sem)

用來釋放信號量sem。

下面我們來看一個使用信號量的例子。在這個例子中,一共有4個線程,其中兩個線程負責從文件讀取數據到公共的緩沖區,另兩個線程從緩沖區讀取數據作不同的處理(加和乘運算)。

/* File sem.c */
#include <stdio.h>
#include <pthread.h>
#include <semaphore.h>

#define MAXSTACK 100
int stack[MAXSTACK][2];
int size=0;
sem_t sem;

/* 從文件1.dat讀取數據,每讀一次,信號量加一*/
void *ReadData1(void *ptr){
    FILE *fp=fopen("1.txt","r");
    while(!feof(fp)){
        fscanf(fp,"%d %d",&stack[size][0],&stack[size][1]);
        sem_post(&sem);
        ++size;
    }
    fclose(fp);
}

/*從文件2.dat讀取數據*/
void *ReadData2(void *ptr){
    FILE *fp=fopen("2.txt","r");
    while(!feof(fp)){
        fscanf(fp,"%d %d",&stack[size][0],&stack[size][1]);
        sem_post(&sem);
        ++size;
    }
    fclose(fp);
}
/*阻塞等待緩沖區有數據,讀取數據后,釋放空間,繼續等待*/
void *HandleData1(void *ptr){
    while(1){
        sem_wait(&sem);
        printf("Plus:%d+%d=%d\n",stack[size][0],stack[size][1],
        stack[size][0]+stack[size][1]);
        --size;
    }
}

void *HandleData2(void *ptr){
    while(1){
        sem_wait(&sem);
        printf("Multiply:%d*%d=%d\n",stack[size][0],stack[size][1],
        stack[size][0]*stack[size][1]);
        --size;
    }
}

int main(void){
    pthread_t t1,t2,t3,t4;
    sem_init(&sem,0,0);
    pthread_create(&t1,NULL,HandleData1,NULL);
    pthread_create(&t2,NULL,HandleData2,NULL);
    pthread_create(&t3,NULL,ReadData1,NULL);
    pthread_create(&t4,NULL,ReadData2,NULL);
    /* 防止程序過早退出,讓它在此無限期等待*/
    pthread_join(t1,NULL);
    pthread_join(t2,NULL);
    pthread_join(t3,NULL);
    pthread_join(t4,NULL);
}

在某種場景下信號量是可以和條件變量互換使用的

信號量和條件變量和線程鎖的區別:

  1. 鎖必須在同一個線程中獲取或者釋放,否則就會死鎖,而條件變量和信號量則不必。
  2. 信號的遞增和減少會被系統自動記住,系統內部有一個計數器實現信號量,不必擔心會丟失,而喚醒一個條件變量時如果沒有相應的線程在等待該條件變量,這次喚醒將丟失。
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,533評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,055評論 3 414
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,365評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,561評論 1 307
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,346評論 6 404
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 54,889評論 1 321
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 42,978評論 3 439
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,118評論 0 286
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,637評論 1 333
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,558評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,739評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,246評論 5 355
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 43,980評論 3 346
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,362評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,619評論 1 280
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,347評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,702評論 2 370

推薦閱讀更多精彩內容

  • 轉自:Youtherhttps://www.cnblogs.com/youtherhome/archive/201...
    njukay閱讀 1,620評論 0 52
  • linux線程同步 信號燈:與互斥鎖和條件變量的主要不同在于"燈"的概念,燈亮則意味著資源可用,燈滅則意味著不可用...
    鮑陳飛閱讀 690評論 0 2
  • 引用自多線程編程指南應用程序里面多個線程的存在引發了多個執行線程安全訪問資源的潛在問題。兩個線程同時修改同一資源有...
    Mitchell閱讀 2,009評論 1 7
  • 線程基礎 線程是進程的一個執行單元,執行一段程序片段,線程共享全局變量;線程的查看可以使用命令或者文件來進行查看;...
    秋風弄影閱讀 745評論 0 0
  • 簡介 線程創建 線程屬性設置 線程參數傳遞 線程優先級 線程的數據處理 線程的分離狀態 互斥鎖 信號量 一 線程創...
    第八區閱讀 8,577評論 1 6