線程的同步

一、互斥量

1、為什么要使用互斥量

當(dāng)多個(gè)線程共享相同的內(nèi)存時(shí),需要每一個(gè)線程看到相同的視圖。當(dāng)一個(gè)線程修改變量時(shí),而其他線程也可以讀取或者修改這個(gè)變量,就需要對(duì)這些線程同步,確保他們不會(huì)訪問到無效的變量。

在變量修改時(shí)間多于一個(gè)存儲(chǔ)器訪問周期的處理器結(jié)構(gòu)中,當(dāng)存儲(chǔ)器的讀和寫這兩個(gè)周期交叉時(shí),這種潛在的不一致性就會(huì)出現(xiàn)。當(dāng)然這與處理器相關(guān),但是在可移植的程序中并不能對(duì)處理器做出任何假設(shè)。

2、互斥鎖的初始化和銷毀

為了讓線程訪問數(shù)據(jù)不產(chǎn)生沖突,這要就需要對(duì)變量加鎖,使得同一時(shí)刻只有一個(gè)線程可以訪問變量。互斥量本質(zhì)就是鎖,訪問共享資源前對(duì)互斥量加鎖,訪問完成后解鎖
當(dāng)互斥量加鎖以后,其他所有需要訪問該互斥量的線程都將阻塞
當(dāng)互斥量解鎖以后,所有因?yàn)檫@個(gè)互斥量阻塞的線程都將變?yōu)榫途w態(tài),第一個(gè)獲得cpu的線程會(huì)獲得互斥量,變?yōu)檫\(yùn)行態(tài),而其他線程會(huì)繼續(xù)變?yōu)樽枞谶@種方式下訪問互斥量每次只有一個(gè)線程能向前執(zhí)行

互斥量用pthread_mutex_t類型的數(shù)據(jù)表示,在使用之前需要對(duì)互斥量初始化
1、如果是動(dòng)態(tài)分配的互斥量,可以調(diào)用pthread_mutex_init()函數(shù)初始化
2、如果是靜態(tài)分配的互斥量,還可以把它置為常量PTHREAD_MUTEX_INITIALIZER
3、動(dòng)態(tài)分配的互斥量在釋放內(nèi)存之前需要調(diào)用pthread_mutex_destroy()

int pthread_mutex_init(pthread_mutex_t *restrict mutex,
      const pthread_mutexattr_t *restrict attr);

int pthread_mutex_destroy(pthread_mutex_t *mutex);

pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;

3、加鎖和解鎖

加鎖:

int pthread_mutex_lock(pthread_mutex_t *mutex); 
成功返回0,失敗返回錯(cuò)誤碼。如果互斥量已經(jīng)被鎖住,那么會(huì)導(dǎo)致該線程阻塞
int pthread_mutex_trylock(pthread_mutex_t *mutex);
成功返回0,失敗返回錯(cuò)誤碼。如果互斥量已經(jīng)被鎖住,不會(huì)導(dǎo)致線程阻塞

解鎖:

int pthread_mutex_unlock(pthread_mutex_t *mutex);
成功返回0,失敗返回錯(cuò)誤碼。如果一個(gè)互斥量沒有被鎖住,那么解鎖就會(huì)出錯(cuò)

二、讀寫鎖

1、什么是讀寫鎖?

讀寫鎖與互斥量類似,不過讀寫鎖有更高的并行性。互斥量要么加鎖要么不加鎖,而且同一時(shí)刻只允許一個(gè)線程對(duì)其加鎖。對(duì)于一個(gè)變量的讀取,完全可以讓多個(gè)線程同時(shí)進(jìn)行操作。

pthread_rwlock_t     rwlock

讀寫鎖有三種狀態(tài),讀模式下加鎖,寫模式下加鎖,不加鎖。一次只有一個(gè)線程可以占有寫模式下的讀寫鎖,但是多個(gè)線程可以同時(shí)占有讀模式的讀寫鎖

讀寫鎖在寫加鎖狀態(tài)時(shí),在它被解鎖之前,所有試圖對(duì)這個(gè)鎖加鎖的線程都會(huì)阻塞。讀寫鎖在讀加鎖狀態(tài)時(shí),所有試圖以讀模式對(duì)其加鎖的線程都會(huì)獲得訪問權(quán),但是如果線程希望以寫模式對(duì)其加鎖,它必須阻塞直到所有的線程釋放鎖。

當(dāng)讀寫鎖以讀模式加鎖時(shí),如果有線程試圖以寫模式對(duì)其加鎖,那么讀寫鎖會(huì)阻塞隨后的讀模式鎖請(qǐng)求。這樣可以避免讀鎖長期占用,而寫鎖達(dá)不到請(qǐng)求。

讀寫鎖非常適合對(duì)數(shù)據(jù)結(jié)構(gòu)讀次數(shù)大于寫次數(shù)的程序,當(dāng)它以讀模式鎖住時(shí),是以共享的方式鎖住的;當(dāng)它以寫模式鎖住時(shí),是以獨(dú)占的模式鎖住的。

2、讀寫鎖的初始化和銷毀

讀寫鎖在使用之前必須初始化:

int pthread_rwlock_init(pthread_rwlock_t *restrict rwlock,
         const pthread_rwlockattr_t *restrict attr);

使用完需要銷毀:

int pthread_rwlock_destroy(pthread_rwlock_t *rwlock); 
成功返回0 ,失敗返回錯(cuò)誤碼

3、加鎖和解鎖

讀模式加鎖:

int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock);
int pthread_rwlock_tryrdlock(pthread_rwlock_t *rwlock);

寫模式加鎖:

int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock);
int pthread_rwlock_trywrlock(pthread_rwlock_t *rwlock);

解鎖:

int pthread_rwlock_unlock(pthread_rwlock_t *rwlock);

成功返回0,錯(cuò)誤返回錯(cuò)誤碼。


三、條件變量

1、條件變量的引入

一個(gè)典型的實(shí)例:
在一條生產(chǎn)先線上有一個(gè)倉庫,當(dāng)生產(chǎn)者生產(chǎn)的時(shí)候需要鎖住倉庫獨(dú)占,而消費(fèi)者取產(chǎn)品的時(shí)候也要鎖住倉庫獨(dú)占。如果生產(chǎn)者發(fā)現(xiàn)倉庫滿了,那么他就不能生產(chǎn)了,變成了阻塞狀態(tài)。但是此時(shí)由于生產(chǎn)者獨(dú)占倉庫,消費(fèi)者又無法進(jìn)入倉庫去消耗產(chǎn)品,這樣就造成了一個(gè)僵死狀態(tài)。

我們需要一種機(jī)制,當(dāng)互斥量被鎖住以后發(fā)現(xiàn)當(dāng)前線程還是無法完成自己的操作,那么它應(yīng)該釋放互斥量,讓其他線程工作。

  1. 可以采用輪詢的方式,不停的查詢你需要的條件
  2. 讓系統(tǒng)來幫你查詢條件,使用條件變量pthread_cond_t cond

2、條件變量的初始化和銷毀

靜態(tài)初始化:
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;

動(dòng)態(tài)初始化:
int pthread_cond_init(pthread_cond_t *restrict cond,
         const pthread_condattr_t *restrict attr);
默認(rèn)屬性為空NULL

條件變量使用完成之后需要銷毀
int pthread_cond_destroy(pthread_cond_t *cond);

3、條件變量的使用

條件變量使用需要配合互斥量使用:

int pthread_cond_wait(pthread_cond_t *restrict cond,
     pthread_mutex_t *restrict mutex);

1、使用pthread_cond_wait等待條件變?yōu)檎妗鬟f給pthread_cond_wait的互斥量對(duì)條件進(jìn)行保護(hù),調(diào)用者把鎖住的互斥量傳遞給函數(shù)。
2、這個(gè)函數(shù)將線程放到等待條件的線程列表上,然后對(duì)互斥量進(jìn)行解鎖,這是個(gè)原子操作。
當(dāng)條件滿足時(shí)這個(gè)函數(shù)返回,返回以后繼續(xù)對(duì)互斥量加鎖。

int pthread_cond_timedwait(pthread_cond_t *restrict cond,
     pthread_mutex_t *restrict mutex,
     const struct timespec *restrict abstime);

這個(gè)函數(shù)與pthread_cond_wait類似,只是多一個(gè)timeout,如果到了指定的時(shí)間條件還不滿足,那么就返回。
時(shí)間用下面的結(jié)構(gòu)體表示:

struct timespec{
    time_t tv_sec;
    long tv_nsec;
 };

注意,這個(gè)時(shí)間是絕對(duì)時(shí)間。例如你要等待3分鐘,就要把當(dāng)前時(shí)間加上3分鐘然后轉(zhuǎn)換到timespec,而不是直接將3分鐘轉(zhuǎn)換到timespec

當(dāng)條件滿足的時(shí)候,需要喚醒等待條件的線程:

int pthread_cond_broadcast(pthread_cond_t *cond);
// pthread_cond_broadcast喚醒等待條件的所有線程
int pthread_cond_signal(pthread_cond_t *cond);
// pthread_cond_signal至少喚醒等待條件的某一個(gè)線程

注意,一定要在條件改變以后再喚醒線程。

典型例子:生產(chǎn)者與消費(fèi)者。

/*
 *DESCRIPTION: 生產(chǎn)者與消費(fèi)者問題
 */

#include <pthread.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <signal.h>
#include <errno.h>

#define BUFFER_SIZE         5       //產(chǎn)品庫存大小  
#define PRODUCT_CNT         30      //產(chǎn)品生產(chǎn)總數(shù)   

struct product_cons  
{  
    int buffer[BUFFER_SIZE];  //生產(chǎn)產(chǎn)品值  
    pthread_mutex_t lock;     //互斥鎖 volatile int  
    int readpos, writepos;    //讀寫位置  
    pthread_cond_t notempty;  //條件變量,非空  
    pthread_cond_t notfull;   //非滿  
}buffer;  

void init(struct product_cons *p)  
{  
    pthread_mutex_init(&p->lock, NULL);     //互斥鎖  
    pthread_cond_init(&p->notempty, NULL);  //條件變量  
    pthread_cond_init(&p->notfull, NULL);   //條件變量  
    p->readpos = 0;                         //讀寫位置  
    p->writepos = 0;  
}  

void finish(struct product_cons *p)  
{  
    pthread_mutex_destroy(&p->lock);     //互斥鎖  
    pthread_cond_destroy(&p->notempty);  //條件變量  
    pthread_cond_destroy(&p->notfull);   //條件變量  
    p->readpos = 0;                      //讀寫位置  
    p->writepos = 0;  
}  


//存儲(chǔ) 一個(gè)數(shù)據(jù) 到 bufferr  
void put(struct product_cons *p, int data) //輸入產(chǎn)品子函數(shù)  
{
    pthread_mutex_lock(&p->lock);
    if((p->writepos+1)%BUFFER_SIZE == p->readpos)
    {
        printf("producer wait for not full\n");
        pthread_cond_wait(&p->notfull, &p->lock);
    }

    p->buffer[p->writepos] = data;
    p->writepos ++;

    if(p->writepos >= BUFFER_SIZE)
        p->writepos = 0;

    pthread_cond_signal(&p->notempty);
    pthread_mutex_unlock(&p->lock);
}  

//讀,移除 一個(gè)數(shù)據(jù) 從 buffer  
int get(struct product_cons *p)  
{
    int data;

    pthread_mutex_lock(&p->lock);

    if(p->readpos == p->writepos)
    {
        printf("consumer wait for not empty\n");
        pthread_cond_wait(&p->notempty, &p->lock);
    }

    data = p->buffer[p->readpos];
    p->readpos++;

    if(p->readpos >= BUFFER_SIZE)
        p->readpos = 0;

    pthread_cond_signal(&p->notfull);

    pthread_mutex_unlock(&p->lock);

    return data;
}  

void *producer(void *data) //子線程 ,生產(chǎn)
{
    int n;  
    for(n = 1; n <= 50; ++n) //生產(chǎn) 50 個(gè)產(chǎn)品  
    {  
        sleep(1);  
        printf("put the %d product ...\n", n);
        put(&buffer,n);  
        printf("put the %d product success\n", n);
    }  

    printf("producer stopped\n");  

    return NULL;  
}  

void *consumer(void *data)  
{  
    static int cnt = 0;  
    int num;
    while(1)  
    {  
        sleep(2);  
        printf("get  product ...\n");  
        num = get(&buffer);
        printf("get the %d product success\n", num);  
        if(++cnt == PRODUCT_CNT)  
            break;  
    }  

    printf("consumer stopped\n");  
    return NULL;  
}  

int main(int argc, char *argv[])  
{  
    pthread_t th_a,th_b;
    void *retval;  

    init(&buffer);  

    pthread_create(&th_a, NULL, producer, 0);  
    pthread_create(&th_b, NULL, consumer, 0);  

    pthread_join(th_a, &retval);   // 等待新線程的結(jié)束
    pthread_join(th_b, &retval);  

    finish(&buffer);  

    return 0;  
}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,797評(píng)論 6 531
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,179評(píng)論 3 414
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,628評(píng)論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,642評(píng)論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 71,444評(píng)論 6 405
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 54,948評(píng)論 1 321
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,040評(píng)論 3 440
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,185評(píng)論 0 287
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 48,717評(píng)論 1 333
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 40,602評(píng)論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 42,794評(píng)論 1 369
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,316評(píng)論 5 358
  • 正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,045評(píng)論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,418評(píng)論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,671評(píng)論 1 281
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 51,414評(píng)論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 47,750評(píng)論 2 370

推薦閱讀更多精彩內(nèi)容