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