柚子快報(bào)邀請(qǐng)碼778899分享:運(yùn)維 linux:線程同步
柚子快報(bào)邀請(qǐng)碼778899分享:運(yùn)維 linux:線程同步
個(gè)人主頁 : 個(gè)人主頁 個(gè)人專欄 : 《數(shù)據(jù)結(jié)構(gòu)》 《C語言》《C++》《Linux》
文章目錄
前言線程同步條件變量接口簡單示例pthread_cond_wait為什么要有mutex偽喚醒問題的解決 (if->while)
總結(jié)
前言
本文作為我對(duì)于線程同步知識(shí)總結(jié)
線程同步
同步:在保證數(shù)據(jù)安全的前提下,讓線程能夠按照某種順序訪問臨界資源,從而有效避免饑餓問題,叫做同步競態(tài)條件:是指多個(gè)線程同時(shí)訪問系統(tǒng)共享資源時(shí),由于其執(zhí)行順序或時(shí)間上的不確定性,導(dǎo)致數(shù)據(jù)不一致或其它不可預(yù)料的結(jié)果(一般發(fā)生在對(duì)稱多處理環(huán)境,中斷和異常處理,內(nèi)核態(tài)搶占,并發(fā)執(zhí)行…)
看了上面兩個(gè)概念,你可能還是不太理解同步是什么,為什么要有同步。下面我們就舉一個(gè)例子。 我們假定有兩個(gè)人,一個(gè)人A將蘋果放在桌子上,另一個(gè)人B蒙著眼睛去桌子上拿蘋果,桌子每次只允許有一個(gè)人,因?yàn)锽不清楚桌子上的情況是什么(有一個(gè)蘋果,沒有蘋果,桌子上全是蘋果),那為了保險(xiǎn)起見,B只能瘋狂的去桌子上拿蘋果,以保證B拿到所有的蘋果。那如果桌子上沒有蘋果,B仍然瘋狂去桌子上拿蘋果,此時(shí)A是不是就不能去桌子上放蘋果,那此時(shí)B是不是再做無用工,而且導(dǎo)致了A不能放蘋果,B拿蘋果的效率降低。如果我們將A,B換成線程,蘋果看出共享資源(某種任務(wù)),桌子看成臨界區(qū),那B是不是就是一直在做申請(qǐng)鎖,再釋放鎖的無效工作,并且導(dǎo)致了A的饑餓問題。這時(shí)我們就需要保證A,B之間的順序問題,如在B訪問過桌子后,不能立即再次訪問桌子,要等待A訪問桌子后。這是不是就會(huì)使A,B拿蘋果的效率提升。而這就是為什么要有同步的理由
條件變量接口
初始化條件變量
靜態(tài)初始化 與互斥鎖類似,定義一個(gè)全局的條件變量,用PTHREAD_COND_INITIALIZER宏來初始化,系統(tǒng)自動(dòng)釋放該條件變量。該宏一般存放在 /usr/include/pthread.h 路徑下 動(dòng)態(tài)初始化 restrict是C語言的一個(gè)關(guān)鍵字,表示該指針是唯一的訪問其指向?qū)ο蟮闹羔槨?cond將被初始化的條件變量,attr 為nullptr,使用默認(rèn)屬性初始化條件變量。 如果函數(shù)成功執(zhí)行,返回0; 如果函數(shù)執(zhí)行失敗,則返回錯(cuò)誤碼,如EAGAIN(資源暫時(shí)不可用),ENOMEM(內(nèi)存不足)
銷毀條件變量 cond 表示將被銷毀的條件變量,需要注意在調(diào)用pthread_cond_destroy后,該指針本身并未被銷毀,只是所指向的條件變量被銷毀,記得將指針置空 如果函數(shù)成功執(zhí)行,返回0; 如果函數(shù)失敗,返回錯(cuò)誤碼。如EBUSY,該條件變量正在被使用
等待條件變量 cond 表示將要等待的條件變量,mutex 表示線程所持有的互斥鎖 如果函數(shù)成功執(zhí)行,返回0;如果函數(shù)執(zhí)行失敗,返回錯(cuò)誤碼,如EINVAL 無效的參數(shù)(條件變量 or 互斥鎖為初始化…)
關(guān)于該函數(shù)的參數(shù)為什么會(huì)有鎖,有什么注意事項(xiàng),在下面代碼示例,我們?cè)俳忉尅?/p>
喚醒等待
cond要發(fā)生信號(hào)的條件變量指針。 如果函數(shù)執(zhí)行成功,返回0; 如果函數(shù)執(zhí)行失敗,返回錯(cuò)誤碼,如ENVAL(無效的參數(shù)) 需要注意的是,pthread_cond_signal只用于喚醒在cond條件變量的阻塞隊(duì)列中等待的一個(gè)線程。
該函數(shù)的參數(shù)與返回值與pthread_cond_signal相同,只不過該函數(shù)喚醒所有在cond條件變量的阻塞隊(duì)列中等待的所有線程,而那些線程先執(zhí)行,取決于操作系統(tǒng)的調(diào)度策略。
簡單示例
我們先來看看下面代碼,3個(gè)線程爭奪ticket資源。當(dāng)三個(gè)線程檢測(cè)到ticket == 0時(shí),三個(gè)線程都將等待。我們主線程每過5秒,使ticket += 10。
#include
#include
#include
#include
using namespace std;
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
int ticket = 100;
void* threadRoutine(void *args)
{
const string threadname = static_cast
while(true)
{
usleep(1000);
pthread_mutex_lock(&mutex);
if(ticket > 0)
{
ticket--;
cout << threadname << ", get a ticket: " << ticket << endl;
}
else
{
cout << threadname << ", ticket == 0" << endl;
pthread_cond_wait(&cond, &mutex);
}
pthread_mutex_unlock(&mutex);
}
return nullptr;
}
int main()
{
pthread_t td1;
pthread_create(&td1, nullptr, threadRoutine, (void*)"thread-1");
pthread_t td2;
pthread_create(&td2, nullptr, threadRoutine, (void*)"thread-2");
pthread_t td3;
pthread_create(&td3, nullptr, threadRoutine, (void*)"thread-3");
while(true)
{
sleep(5);
pthread_mutex_lock(&mutex);
ticket += 10;
pthread_mutex_unlock(&mutex);
pthread_cond_signal(&cond);
}
pthread_join(td1, nullptr);
pthread_join(td2, nullptr);
pthread_join(td3, nullptr);
return 0;
}
當(dāng)線程2,線程3,線程1先后檢測(cè)到ticket == 0時(shí),在cond的等待隊(duì)列中,線程以2,3,1的順序排隊(duì)。那當(dāng)調(diào)用pthread_cond_signal函數(shù)時(shí),線程2會(huì)先執(zhí)行,執(zhí)行完再檢測(cè)到ticket==0,排到等待隊(duì)列尾部 再調(diào)用pthread_cond_signal函數(shù)時(shí),線程3會(huì)執(zhí)行。 再調(diào)用pthread_cond_signal函數(shù)時(shí),線程1會(huì)執(zhí)行。 這樣,我們多線程就可以按某種特定順序來執(zhí)行。 那如果我們使用pthread_cond_broadcast函數(shù),會(huì)有什么情況? 我們會(huì)發(fā)現(xiàn),三個(gè)線程都被喚醒,來爭搶ticket資源,其先后順序由操作系統(tǒng)決定(優(yōu)先級(jí),競爭鎖的能力…)。
pthread_cond_wait為什么要有mutex
此時(shí)不知道你是否有一個(gè)疑問,我們假定線程-1先爭搶到ticket,檢測(cè)到ticket == 0時(shí),該線程-1要在cond的等待隊(duì)列中等待,然后其它兩個(gè)線程在進(jìn)入臨界區(qū),檢測(cè)到ticket==0,在等待隊(duì)列中排隊(duì)。但是線程-1是持有鎖進(jìn)入等待隊(duì)列的,那其它兩個(gè)線程是如何進(jìn)入臨界區(qū)的?答案很明顯,那就是線程-1在cond等待中,一定釋放了其持有的鎖,從而使其余兩個(gè)線程可以持有鎖進(jìn)入臨界區(qū)。這就是為什么pthread_cond_wait函數(shù)的參數(shù)要有互斥鎖的存在。 那我們?cè)谏钊胂胍幌耄{(diào)用pthread_cond_wait函數(shù)后,該線程是先釋放鎖,再進(jìn)入等待隊(duì)列中;還是先進(jìn)入等待隊(duì)列中,再釋放鎖?答案是都不是。釋放鎖和進(jìn)入等待隊(duì)列是同時(shí)進(jìn)行的?。。∵@也表示pthread_cond_wait函數(shù)是原子的,在調(diào)用pthread_cond_wait時(shí),涉及的互斥鎖釋放和進(jìn)入等待隊(duì)列的操作是作為一個(gè)不可分割的整體來執(zhí)行。確保了線程在調(diào)用該函數(shù)時(shí)不會(huì)遇到競態(tài)條件,即線程在釋放鎖和進(jìn)入等待隊(duì)列之間不會(huì)被其它線程打斷。 以下是線程調(diào)用pthread_cond_wait的過程
線程必須已經(jīng)持有鎖:在調(diào)用pthread_cond_wait時(shí),線程必須已經(jīng)鎖定了某個(gè)互斥鎖,這是該函數(shù)的前提條件 自動(dòng)釋放互斥鎖:當(dāng)線程調(diào)用pthread_cond_wait時(shí),它會(huì)自動(dòng)釋放它當(dāng)前持有的互斥鎖。這一步是為了允許其它線程有機(jī)會(huì)獲取該互斥鎖并修改共享資源,從而可能改變條件變量的狀態(tài) 加入等待隊(duì)列:釋放互斥鎖之后,線程會(huì)接著被添加到條件變量的等待隊(duì)列中,并在此處等待 等待被喚醒:線程在等待隊(duì)列中等待,直到其它線程調(diào)用pthread_cond_signal 或 pthread_cond_broadcast來喚醒它 重新獲取互斥鎖:當(dāng)線程被喚醒時(shí),線程會(huì)嘗試重新獲取之前釋放的互斥鎖。如果鎖此時(shí)沒有被其它線程持有,線程將成功獲取鎖并繼續(xù)執(zhí)行。如果鎖仍然被其它線程持有,線程將阻塞(在申請(qǐng)鎖的地方阻塞),直到能夠獲取鎖時(shí)。
具體來說,pthread_cond_wait函數(shù)的內(nèi)部實(shí)現(xiàn)保證了2,3步驟的原子性。
偽喚醒問題的解決 (if->while)
偽喚醒問題是指:在多線程環(huán)境中,當(dāng)線程等待某個(gè)條件變量時(shí),它可能會(huì)在沒有到達(dá)預(yù)期的情況下被喚醒。 對(duì)于這一問題,我們可以在判斷的時(shí)候,將if 變?yōu)?while,使其被喚醒后任然進(jìn)行條件判斷,如果條件滿足,線程繼續(xù)向后執(zhí)行,如果條件不被滿足,線程繼續(xù)在該條件變量下等待。
pthread_mutex_lock(&mutex);
// 訪問臨界區(qū)
while(條件為假)
pthread_cond_wait(&cond, &mutex);
pthread_mutex_unlock(&mutex);
總結(jié)
以上就是我對(duì)于線程同步的總結(jié)。
柚子快報(bào)邀請(qǐng)碼778899分享:運(yùn)維 linux:線程同步
精彩內(nèi)容
本文內(nèi)容根據(jù)網(wǎng)絡(luò)資料整理,出于傳遞更多信息之目的,不代表金鑰匙跨境贊同其觀點(diǎn)和立場。
轉(zhuǎn)載請(qǐng)注明,如有侵權(quán),聯(lián)系刪除。