pthread 線程同步

雖然本身是做Android開發(fā)的, 但經(jīng)常會(huì)用到C/C++, 最近項(xiàng)目中剛好通過線程同步解決了一個(gè)問題,線程知識(shí)應(yīng)用太廣泛了, 所以在此記錄下關(guān)于C/C++中比較實(shí)用基礎(chǔ)知識(shí), 本篇文章就說明一下pthread中線程同步的幾種方式.

pthread

pthread 即 POSIX threads, POSIX表示可移植操作系統(tǒng)接口(Portable Operating System Interface of UNIX), 所以pthread就是在這個(gè)標(biāo)準(zhǔn)下實(shí)現(xiàn)的線程, 廣泛用于類Unix操作系統(tǒng)上, 使用需要引入的頭文件為 #include <pthread.h>基本使用示例代碼如下:

#include <stdio.h>
#include <pthread.h>

int count = 0;

void* test_func_a(void* ptr);

void main() {
    int loopCount = 5;
    pthread_t thread[5];
    char args[loopCount][10];
    for (int i = 0; i < loopCount; i++) {
        int err;
        sprintf(args[i], "Thread %d ", i);
        err = pthread_create(&thread[i], NULL, test_func_a, (void*) args[i]);
        if (err) {
            printf("create pthread failed ret:%d \n", err);
        }
    }
    for (int i = 0; i < loopCount; i++) {
        pthread_join(thread[i], NULL);
    }
}

void* test_func_a(void* ptr) {
    char* msg = (char*) ptr;
    count++;
    printf("%s value:%d \n", msg, count);
}

pthread創(chuàng)建很簡單, 調(diào)用pthread_create()即可, 函數(shù)定義如下:

int pthread_create(pthread_t * thread, 
                       const pthread_attr_t * attr,
                       void * (*start_routine)(void *), 
                       void *arg);

參數(shù)1: 存儲(chǔ)創(chuàng)建線程的id
參數(shù)2:一些線程屬性, 如果只是普通使用, 傳NULL
參數(shù)3: 函數(shù)指針, 即你要在此線程中運(yùn)行的函數(shù)
參數(shù)4:用于傳遞參數(shù)到運(yùn)行的函數(shù)中

注意:如果你是C/C++混合編程, 第三個(gè)參數(shù)在C++中只能傳遞全局函數(shù)或者類的靜態(tài)函數(shù), 不能傳類的普通成員函數(shù), 因?yàn)槠胀惓蓡T函數(shù)是屬于對(duì)象的, 類沒有實(shí)例化是不能使用這個(gè)函數(shù)的.

pthread_create()創(chuàng)建線程后, 線程會(huì)立即運(yùn)行, 通過調(diào)用pthread_join()等待線程結(jié)束, 此函數(shù)會(huì)阻塞當(dāng)前線程, pthread_join()成功返回后, 線程資源就會(huì)被釋放, 上面的示例代碼,編譯(編譯要加-pthread參數(shù))運(yùn)行后輸出結(jié)果是不確定的, 原因是多個(gè)線程沒有同步, 造成一些不可預(yù)料的結(jié)果發(fā)生, 其中某次輸出結(jié)果如下:

 $ ./test_sync
Thread 0  value:1
Thread 2  value:3
Thread 1  value:2
Thread 3  value:4
Thread 4  value:5

下面開始講線程同步的問題.

線程同步-Joins

pthread_join()大多數(shù)情況下都是用來結(jié)束線程的, 即退出程序釋放相關(guān)線程, 但也可以作為簡單同步功能來使用, 比如將上面示例代碼修改為如下方式:

#include <stdio.h>
#include <pthread.h>


int count = 0;

void* test_func_a(void* ptr);

void main() {
    int loopCount = 5;
    pthread_t thread[5];
    char args[loopCount][10];
    for (int i = 0; i < loopCount; i++) {
        int err;
        sprintf(args[i], "Thread %d ", i);
        err = pthread_create(&thread[i], NULL, test_func_a, (void*) args[i]);
        if (err) {
            printf("create pthread failed ret:%d \n", err);
        }
        //前一個(gè)線程結(jié)束后才運(yùn)行下一個(gè)線程
        pthread_join(thread[i], NULL);
    }
    /*for (int i = 0; i < loopCount; i++) {
        pthread_join(thread[i], NULL);
    }*/
}

void* test_func_a(void* ptr) {
    char* msg = (char*) ptr;
    count++;
    printf("%s value:%d \n", msg, count);
}

這樣修改后每次結(jié)果都是確定的, 原因是我們等前一個(gè)線程運(yùn)行完成后,才啟動(dòng)下一個(gè)線程, 之前是一次性啟動(dòng), 運(yùn)行結(jié)果每次都是:

 $ ./test_join
Thread 0  value:1
Thread 1  value:2
Thread 2  value:3
Thread 3  value:4
Thread 4  value:5

此方式在時(shí)間項(xiàng)目中使用場(chǎng)景有限, 很少使用.

線程同步-Mutexes

Mutex即互斥量, 如果上鎖后, 其他線程則無法獲得鎖導(dǎo)致線程阻塞, 直到鎖被釋放,才能再次獲得鎖進(jìn)而執(zhí)行相關(guān)代碼, 示例代碼:

#include <stdio.h>
#include <pthread.h>


int count = 0;
pthread_mutex_t mLock;

void* test_func_a(void* ptr);

void main() {
    int loopCount = 5;
    pthread_t thread[5];
    char args[loopCount][10];
    pthread_mutex_init(&mLock, NULL);
    for (int i = 0; i < loopCount; i++) {
        int err;
        sprintf(args[i], "Thread %d ", i);
        err = pthread_create(&thread[i], NULL, test_func_a, (void*) args[i]);
        if (err) {
            printf("create pthread failed ret:%d \n", err);
        }
    }
    for (int i = 0; i < loopCount; i++) {
        pthread_join(thread[i], NULL);
    }
    pthread_mutex_destroy(&mLock);
}

void* test_func_a(void* ptr) {
    pthread_mutex_lock(&mLock);
    char* msg = (char*) ptr;
    count++;
    printf("%s value:%d \n", msg, count);
    pthread_mutex_unlock(&mLock);
}

使用流程如下:
1.定義 pthread_mutex_t pthread_mutex_t mLock;

  1. 初始化有兩種方式, 調(diào)用pthread_mutex_init(&mLock, NULL);或者mLock = PTHREAD_MUTEX_INITIALIZER;效果一樣, 后者是通過定義的宏來實(shí)現(xiàn)的.
  2. 調(diào)用lock和unlock函數(shù), 上鎖(獲得鎖)pthread_mutex_lock(&mLock);, 解鎖(釋放鎖) pthread_mutex_unlock(&mLock);, 同一時(shí)間只有一個(gè)線程能獲得該鎖, 被lock后, 其他線程調(diào)用lock函數(shù)會(huì)阻塞當(dāng)前線程.
  3. 釋放定義的pthread_mutex_t 資源, 調(diào)用pthread_mutex_destroy(&mLock);

上面代碼運(yùn)行結(jié)果如下:

 $ ./test_mutex
Thread 0  value:1
Thread 1  value:2
Thread 2  value:3
Thread 3  value:4
Thread 4  value:5

注意: 上面的例子只能保證value的值是按照預(yù)期從1變?yōu)?的, 但并不能保證thread的運(yùn)行順序,也就是說你運(yùn)行的結(jié)果有可能是下面這樣的:

 $ ./test_mutex
Thread 0  value:1
Thread 2  value:2
Thread 1  value:3
Thread 3  value:4
Thread 4  value:5

原因是上面的例子并不能保證各個(gè)線程獲取鎖的順序, 因?yàn)槊總€(gè)線程獲得鎖的優(yōu)先級(jí)是相同的, 所以順序有可能每次都不一樣.

線程同步-Condition Variables

Condition Variables 即條件變量, 在線程同步使用過程中要配合上面的mutex進(jìn)行使用, 實(shí)際多線程開發(fā)使用較多, 比如經(jīng)典的生產(chǎn)者-消費(fèi)者關(guān)系就可以通過Condition Variables來實(shí)現(xiàn), 常用的場(chǎng)景為滿足某個(gè)條件讓當(dāng)前線程阻塞進(jìn)行等待, 當(dāng)其他線程滿足另一個(gè)條件后, 通知正在等待的線程進(jìn)行工作.
下面通過一個(gè)簡單例子來說明下基本使用:

#include <stdio.h>
#include <pthread.h>


int count = 0;
pthread_mutex_t mLock;
pthread_cond_t mCond;

void* test_func_a(void* ptr);

void main() {
    int loopCount = 5;
    pthread_t thread[5];
    char args[loopCount][10];
    pthread_mutex_init(&mLock, NULL);
    pthread_cond_init(&mCond, NULL);
    for (int i = 0; i < loopCount; i++) {
        int err;
        sprintf(args[i], "Thread %d ", i);
        err = pthread_create(&thread[i], NULL, test_func_a, (void*) args[i]);
        if (err) {
            printf("create pthread failed ret:%d \n", err);
        }
    }
    printf("sleep 1s start \n");
    sleep(1);
    printf("sleep 1s end, call pthread_cond_signal() \n");
    pthread_mutex_lock(&mLock);
    //喚醒所有等待mCond的線程
    pthread_cond_broadcast(&mCond);
    //喚醒一個(gè)線程,如果當(dāng)前有多個(gè)線程等待,
    //根據(jù)優(yōu)先級(jí)和等待時(shí)間選擇其中一個(gè)線程進(jìn)行喚醒
    //pthread_cond_signal(&mCond);
    pthread_mutex_unlock(&mLock);
    for (int i = 0; i < loopCount; i++) {
        pthread_join(thread[i], NULL);
    }
    pthread_mutex_destroy(&mLock);
    pthread_cond_destroy(&mCond);
}

void* test_func_a(void* ptr) {
    pthread_mutex_lock(&mLock);
    pthread_cond_wait(&mCond, &mLock);
    pthread_mutex_unlock(&mLock);
    char* msg = (char*) ptr;
    count++;
    printf("%s value:%d \n", msg, count);
}

使用流程和mutex差不多,如下:

  1. 定義 pthread_cond_t pthread_cond_t mCond;
  2. 初始化也和mutex一樣兩種方式 pthread_cond_init(&mCond, NULL);mCond = PTHREAD_COND_INITIALIZER;
  3. 讓當(dāng)前線程阻塞進(jìn)入等待狀態(tài)
    pthread_mutex_lock(&mLock);
    pthread_cond_wait(&mCond, &mLock);
    pthread_mutex_unlock(&mLock);

注意, 此處必須和mutex一起使用, 即調(diào)用pthread_cond_wait()這個(gè)函數(shù)本身要上鎖, 否則會(huì)產(chǎn)生不可預(yù)料異常.

  1. 喚醒等待此條件變量的線程
    pthread_mutex_lock(&mLock);
    //喚醒所有等待mCond的線程
    pthread_cond_broadcast(&mCond);
    //喚醒一個(gè)線程,如果當(dāng)前有多個(gè)線程等待,
    //根據(jù)優(yōu)先級(jí)和等待時(shí)間選擇其中一個(gè)線程進(jìn)行喚醒
    //pthread_cond_signal(&mCond);
    pthread_mutex_unlock(&mLock);
  1. 釋放資源 pthread_cond_destroy(&mCond);

上面示例代碼基本邏輯是啟動(dòng)五個(gè)線程, 默認(rèn)開始就阻塞(等待mCond), 然后主線程sleep 1s后, 喚醒所有等待的線程, 此時(shí)5個(gè)線程會(huì)同時(shí)運(yùn)行同一個(gè)函數(shù), 輸出結(jié)果不可預(yù)料, 某次結(jié)果如下:

 $ ./test_cond
sleep 1s start
sleep 1s end, call pthread_cond_signal()
Thread 0  value:1
Thread 3  value:4
Thread 1  value:2
Thread 2  value:3
Thread 4  value:5

總結(jié)

根據(jù)我自己遇到的一些多線程問題, 我覺得多線程開發(fā)需要注意一下幾點(diǎn):

  1. 不要以常規(guī)思維思考沒做過同步的一些代碼的運(yùn)行結(jié)果, 很多結(jié)果你自己是沒法預(yù)料的.
  2. 寫代碼時(shí)思路要清晰, 有l(wèi)ock就要保證能在合適時(shí)機(jī)unlock, 不然很容易出現(xiàn)死鎖.
  3. 線程參數(shù)傳遞大多數(shù)是通過指針來實(shí)現(xiàn)的, 需要注意這些參數(shù)的生命周期, C/C++和Java不同,
    Java中只要有引用對(duì)象就不會(huì)被釋放, 但C/C++中則不同, 超出作用域或者手動(dòng)釋放, 相關(guān)資源都會(huì)變?yōu)椴豢捎? 由于線程運(yùn)行時(shí)間大多數(shù)不是立即運(yùn)行的, 所以這種問題也比較常見.

本文講了三種線程同步方式 Joins, Mutexes和Condition Variables, 實(shí)際項(xiàng)目中后兩個(gè)用的非常多, join更多的用在最后釋放資源的時(shí)候用, 示例代碼都是非常簡單的基本使用方法, 還有很多高級(jí)用法沒有說明, 有興趣可自行查閱, 這里推薦一個(gè)非常不錯(cuò)的網(wǎng)站 http://www.yolinux.com/TUTORIALS/LinuxTutorialPosixThreads.html

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末柒凉,一起剝皮案震驚了整個(gè)濱河市甫恩,隨后出現(xiàn)的幾起案子含潘,更是在濱河造成了極大的恐慌,老刑警劉巖丧失,帶你破解...
    沈念sama閱讀 218,755評(píng)論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡波势,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,305評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門涣雕,熙熙樓的掌柜王于貴愁眉苦臉地迎上來艰亮,“玉大人,你說我怎么就攤上這事挣郭∑#” “怎么了?”我有些...
    開封第一講書人閱讀 165,138評(píng)論 0 355
  • 文/不壞的土叔 我叫張陵兑障,是天一觀的道長侄非。 經(jīng)常有香客問我,道長流译,這世上最難降的妖魔是什么逞怨? 我笑而不...
    開封第一講書人閱讀 58,791評(píng)論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮福澡,結(jié)果婚禮上叠赦,老公的妹妹穿的比我還像新娘。我一直安慰自己革砸,他們只是感情好除秀,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,794評(píng)論 6 392
  • 文/花漫 我一把揭開白布糯累。 她就那樣靜靜地躺著,像睡著了一般册踩。 火紅的嫁衣襯著肌膚如雪泳姐。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,631評(píng)論 1 305
  • 那天暂吉,我揣著相機(jī)與錄音胖秒,去河邊找鬼。 笑死慕的,一個(gè)胖子當(dāng)著我的面吹牛阎肝,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播业稼,決...
    沈念sama閱讀 40,362評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼盗痒,長吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了低散?” 一聲冷哼從身側(cè)響起俯邓,我...
    開封第一講書人閱讀 39,264評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎熔号,沒想到半個(gè)月后稽鞭,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,724評(píng)論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡引镊,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,900評(píng)論 3 336
  • 正文 我和宋清朗相戀三年朦蕴,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片弟头。...
    茶點(diǎn)故事閱讀 40,040評(píng)論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡吩抓,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出赴恨,到底是詐尸還是另有隱情疹娶,我是刑警寧澤,帶...
    沈念sama閱讀 35,742評(píng)論 5 346
  • 正文 年R本政府宣布伦连,位于F島的核電站雨饺,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏惑淳。R本人自食惡果不足惜额港,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,364評(píng)論 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望歧焦。 院中可真熱鬧移斩,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,944評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至风罩,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間舵稠,已是汗流浹背超升。 一陣腳步聲響...
    開封第一講書人閱讀 33,060評(píng)論 1 270
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留哺徊,地道東北人室琢。 一個(gè)月前我還...
    沈念sama閱讀 48,247評(píng)論 3 371
  • 正文 我出身青樓,卻偏偏與公主長得像落追,于是被迫代替她去往敵國和親盈滴。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,979評(píng)論 2 355

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

  • 多線程系列文章源碼頭文件內(nèi)容: #include #include #include 作為程序員轿钠,就是要減少重復(fù)勞...
    batbattle閱讀 923評(píng)論 0 1
  • 轉(zhuǎn)自:Youtherhttps://www.cnblogs.com/youtherhome/archive/201...
    njukay閱讀 1,616評(píng)論 0 52
  • Q:為什么出現(xiàn)多線程巢钓? A:為了實(shí)現(xiàn)同時(shí)干多件事的需求(并發(fā)),同時(shí)進(jìn)行著下載和頁面UI刷新疗垛。對(duì)于處理器症汹,為每個(gè)線...
    幸福相依閱讀 1,582評(píng)論 0 2
  • 翻譯:Synchronization 同步 應(yīng)用程序中存在多個(gè)線程會(huì)導(dǎo)致潛在的問題,這些問題可能會(huì)導(dǎo)致從多個(gè)執(zhí)行線...
    AlexCorleone閱讀 2,492評(píng)論 0 4
  • 摘要 線程概念,線程與進(jìn)程的區(qū)別與聯(lián)系學(xué)會(huì)線程控制,線程創(chuàng)建,線程終止,線程等待了解線程分離與線程安全學(xué)會(huì)線程同步...
    狼之足跡閱讀 465評(píng)論 2 3