共享數(shù)據(jù)和線程

共享數(shù)據(jù)帶來的問題

條件競(jìng)爭(zhēng)

條件競(jìng)爭(zhēng)產(chǎn)生

并發(fā)中競(jìng)爭(zhēng)條件的形成,取決于一個(gè)以上線程的相對(duì)執(zhí)行順序,每個(gè)線程都搶著完成自己的任務(wù)滞造。大多數(shù)情況下路召,即使改變執(zhí)行順序勃刨,也是良性競(jìng)爭(zhēng)波材,結(jié)果是可以接受的。例如有兩個(gè)線程同時(shí)向一個(gè)處理隊(duì)列中添加任務(wù)身隐,由于系統(tǒng)提供的不變量保持不變廷区,所以先后順序并不會(huì)造成影響。不變量遭到破壞以后贾铝,才會(huì)產(chǎn)生條件競(jìng)爭(zhēng)躲因,這種競(jìng)爭(zhēng)方式通常表示為惡性條件競(jìng)爭(zhēng)。
惡性條件競(jìng)爭(zhēng)通常發(fā)生于對(duì)多于一個(gè)的數(shù)據(jù)塊的修改的時(shí)候忌傻,操作訪問兩個(gè)獨(dú)立的數(shù)據(jù)塊大脉,獨(dú)立的指令將會(huì)對(duì)數(shù)據(jù)塊進(jìn)行修改,并且可能一個(gè)線程正在進(jìn)行時(shí)水孩,另一個(gè)線程就對(duì)數(shù)據(jù)塊進(jìn)行了訪問镰矿。因?yàn)槌霈F(xiàn)的概率太小,條件競(jìng)爭(zhēng)很難查找俘种,并且也很難進(jìn)行浮現(xiàn)秤标。當(dāng)系統(tǒng)負(fù)載增加的時(shí)候,隨著執(zhí)行數(shù)量的增加宙刘,執(zhí)行序列的問題復(fù)現(xiàn)的概率也在增加苍姜,但是這只會(huì)出現(xiàn)在負(fù)載比較大的時(shí)候。條件競(jìng)爭(zhēng)通常是時(shí)間敏感的悬包,所以程序以調(diào)試模式進(jìn)行運(yùn)行的時(shí)候衙猪,他們將會(huì)完全消失。

避免惡性條件競(jìng)爭(zhēng)

  • 對(duì)數(shù)據(jù)結(jié)構(gòu)采用某種保護(hù)機(jī)制布近,確保只有進(jìn)行修改的線程才能看到不變量被破壞的中間狀態(tài)垫释。從其他的線程來看,修改不是已經(jīng)完成的撑瞧,就是還沒有開始棵譬。
  • 對(duì)數(shù)據(jù)結(jié)構(gòu)和不變量的設(shè)計(jì)進(jìn)行修改,修改完成的結(jié)構(gòu)必須可以完成一系列不可分割的變化预伺,也就是保證每個(gè)不變量保持穩(wěn)定的狀態(tài)订咸,即所謂的無鎖編程。
  • 使用事物的方式進(jìn)行數(shù)據(jù)結(jié)構(gòu)的更新酬诀。當(dāng)所需的一些數(shù)據(jù)和讀取都存儲(chǔ)在事務(wù)日志中脏嚷,然后將之前的操作合并為一步再進(jìn)行提交。當(dāng)數(shù)據(jù)結(jié)構(gòu)被另一個(gè)線程修改之后料滥,或者處理已經(jīng)重啟的情況下然眼,提交就會(huì)無法進(jìn)行。這被稱作“軟件事物內(nèi)存”

使用互斥量保護(hù)數(shù)據(jù)

使用互斥量保護(hù)共享數(shù)據(jù)

當(dāng)訪問共享數(shù)據(jù)的前葵腹,使用互斥量將相關(guān)的數(shù)據(jù)鎖住高每,當(dāng)訪問結(jié)束之后屿岂,再將數(shù)據(jù)進(jìn)行解鎖。當(dāng)一個(gè)線程使用特定互斥量鎖住共享數(shù)據(jù)時(shí)候鲸匿,其他的線程想要訪問鎖住的數(shù)據(jù)爷怀,都必須等到之前的那個(gè)線程對(duì)數(shù)據(jù)進(jìn)行解鎖后,才能進(jìn)行訪問带欢。這就保證了所有線程都可以看到共享數(shù)據(jù)运授,而不破壞不變量。

C++中使用互斥量

C++中通過實(shí)例化std::mutex創(chuàng)建互斥量乔煞,通過調(diào)用成員函數(shù)lock()進(jìn)行上鎖吁朦,unlock()進(jìn)行解鎖。C++標(biāo)準(zhǔn)庫為互斥量提供了一個(gè)RAII語法的模板類std::lack_guard渡贾,其會(huì)在構(gòu)造的時(shí)候提供已經(jīng)上鎖的互斥量逗宜,并且在析構(gòu)的時(shí)候進(jìn)行解鎖,從而保證了一個(gè)已經(jīng)上鎖的互斥量都會(huì)被正確的解鎖空骚。

#include<mutex>
#include<list>
#include<algorithm>
using namespace std;
std::list<int> some_list;
std::mutex some_mutex;

void add_to_list(int new_value){
std::lock_guard<std::mutex> guard(some_mutex);
some_list.push_back(new_value);
}

void list_contains(int value_to_find){
std::lock_guard<std::mutex> guard(some_mutex);

return std::find(some_list.begin(),some_list.end(),value_to_find)!=some_list.end();
}

在某些情況下纺讲,使用全局變量沒有問題,但是在大多數(shù)的情況下囤屹,互斥量通常會(huì)和保護(hù)的數(shù)據(jù)放在同一個(gè)類中熬甚,而不是定義成全局變量。
將其放在同一個(gè)類中肋坚,就可以將其聯(lián)系在一起乡括,也可以對(duì)類的功能進(jìn)行封裝,并且進(jìn)行數(shù)據(jù)的保護(hù)冲簿。在這種情況下粟判,函數(shù)add_to_list和list_contains都可以作為這個(gè)類的成員函數(shù)。 互斥量和要保護(hù)的數(shù)據(jù)峦剔,在類中都要定義為private成員。當(dāng)所有成員函數(shù)都會(huì)在調(diào)用的時(shí)候?qū)?shù)據(jù)進(jìn)行上鎖角钩,結(jié)束的時(shí)候?qū)?shù)據(jù)進(jìn)行解鎖吝沫,就可以保證數(shù)據(jù)訪問的時(shí)候,不變量不會(huì)被破壞递礼。

#include<iostream>
#include<thread>
#include<mutex>
using namespace std;

class some_data{
    int a;
    string b;
public:
some_data():a(10),b("This is a test"){};
void do_something(){
    cout<<b<<"\n";
    cout<<a<<"\n";
};
void reset_a(int x){
    a=x;
}
void reset_b(string s){
    b=s;
}

};

//構(gòu)造一個(gè)data_wrapper 類對(duì)data數(shù)據(jù)結(jié)構(gòu)加以保護(hù)
//同時(shí)可以使用參數(shù)傳遞的方式 傳入處理函數(shù)
//這里只使用了一個(gè)互斥量 保護(hù)的是data整體
class data_wrapper{
private:
    some_data data;
    std::mutex m;
public:
    template<typename Function>
    void process_data(Function func){
    std::lock_guard<std::mutex> l(m);
    func(data);
}
};

void malicious_function(some_data& protected_data){
    protected_data.do_something();
    protected_data.reset_a(10);
    protected_data.reset_b("another test");
    protected_data.do_something();
}


int main(){
data_wrapper x;

x.process_data(malicious_function);
return 0;
}

當(dāng)其中的一個(gè)成員函數(shù)返回的是保護(hù)數(shù)據(jù)的指針或者引用的時(shí)候惨险,會(huì)破壞對(duì)數(shù)據(jù)的保護(hù)。

精心組織代碼來保護(hù)共享數(shù)據(jù)

使用互斥量保護(hù)數(shù)據(jù)脊髓,并不是僅僅在每一個(gè)成員函數(shù)中都加入一個(gè)std::lock_guard對(duì)象那么簡(jiǎn)單辫愉。一個(gè)迷失的指針或者引用,會(huì)將這種保護(hù)形同虛設(shè)将硝。只要沒有成員函數(shù)通過返回值或者是輸出參數(shù)的形式向其調(diào)用者返回指向受保護(hù)數(shù)據(jù)的指針或者引用恭朗,數(shù)據(jù)就是安全的屏镊。
在確保成員函數(shù)不會(huì)傳出指針或者引用的同時(shí),檢查成員函數(shù)是否通過指針或者引用的方式來調(diào)用也是重要的痰腮。函數(shù)可能沒在互斥量保護(hù)的區(qū)域內(nèi)而芥,存儲(chǔ)著指針或者引用,這樣就是危險(xiǎn)的膀值。更為危險(xiǎn)的是棍丐,將保護(hù)數(shù)據(jù)作為一個(gè)運(yùn)行時(shí)參數(shù)。

#include<iostream>
#include<thread>
#include<mutex>
using namespace std;

class some_data{
    int a;
    string b;
public:
some_data():a(10),b("This is a test"){};
void do_something(){
    cout<<b<<"\n";
    cout<<a<<"\n";
};
void reset_a(int x){
    a=x;
}
void reset_b(string s){
    b=s;
}

};

//構(gòu)造一個(gè)data_wrapper 類對(duì)data數(shù)據(jù)結(jié)構(gòu)加以保護(hù)
//同時(shí)可以使用參數(shù)傳遞的方式 傳入處理函數(shù)
//這里只使用了一個(gè)互斥量 保護(hù)的是data整體
class data_wrapper{
private:
    some_data data;
    std::mutex m;
public:
    template<typename Function>
    void process_data(Function func){
    std::lock_guard<std::mutex> l(m);
    func(data);
}
};
//這里使用了一個(gè)不受保護(hù)的指針 導(dǎo)致 在沒有互斥量的情況下 也可以進(jìn)行對(duì)函數(shù)的訪問和改變
//是錯(cuò)誤的需要進(jìn)行避免的操作
some_data * unprotected_data;
void malicious_function(some_data& protected_data){
    protected_data.do_something();
    protected_data.reset_a(10);
    protected_data.reset_b("another test");
    protected_data.do_something();
    unprotected_data=&protected_data;
}


int main(){
data_wrapper x;

x.process_data(malicious_function);
unprotected_data.do_something();
return 0;
}
使用接口內(nèi)在的競(jìng)爭(zhēng)條件

在使用了互斥量或者是其他機(jī)制保護(hù)共享數(shù)據(jù)的時(shí)候沧踏,也需要避免接口內(nèi)部的條件競(jìng)爭(zhēng)歌逢。
以雙向鏈表為例,為了可以線程安全的刪除一個(gè)節(jié)點(diǎn)翘狱,需要確保防止對(duì)三個(gè)節(jié)點(diǎn)(待刪除節(jié)點(diǎn)以及前后相鄰的節(jié)點(diǎn))的并發(fā)訪問趋翻。如果只是對(duì)指向每個(gè)節(jié)點(diǎn)的指針進(jìn)行了訪問保護(hù),就和沒有使用互斥量是一樣的盒蟆,條件競(jìng)爭(zhēng)仍然是存在的踏烙。除了指針,整個(gè)數(shù)據(jù)結(jié)構(gòu)和整個(gè)刪除操作都是需要進(jìn)行保護(hù)的历等。這種情況下最簡(jiǎn)單的解決方案就是使用互斥量保護(hù)整個(gè)鏈表讨惩。
構(gòu)建一個(gè)類似于std::stack結(jié)構(gòu)的棧,除了構(gòu)造函數(shù)和swap()操作之外寒屯,還需要提供push(),pop(),top()等操作荐捻。

數(shù)據(jù)保護(hù)的替代方案

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市寡夹,隨后出現(xiàn)的幾起案子处面,更是在濱河造成了極大的恐慌,老刑警劉巖菩掏,帶你破解...
    沈念sama閱讀 218,525評(píng)論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件魂角,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡智绸,警方通過查閱死者的電腦和手機(jī)野揪,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,203評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來瞧栗,“玉大人斯稳,你說我怎么就攤上這事〖?郑” “怎么了挣惰?”我有些...
    開封第一講書人閱讀 164,862評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我憎茂,道長(zhǎng)珍语,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,728評(píng)論 1 294
  • 正文 為了忘掉前任唇辨,我火速辦了婚禮廊酣,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘赏枚。我一直安慰自己亡驰,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,743評(píng)論 6 392
  • 文/花漫 我一把揭開白布饿幅。 她就那樣靜靜地躺著凡辱,像睡著了一般。 火紅的嫁衣襯著肌膚如雪栗恩。 梳的紋絲不亂的頭發(fā)上透乾,一...
    開封第一講書人閱讀 51,590評(píng)論 1 305
  • 那天,我揣著相機(jī)與錄音磕秤,去河邊找鬼乳乌。 笑死,一個(gè)胖子當(dāng)著我的面吹牛市咆,可吹牛的內(nèi)容都是我干的汉操。 我是一名探鬼主播,決...
    沈念sama閱讀 40,330評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼蒙兰,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼磷瘤!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起搜变,我...
    開封第一講書人閱讀 39,244評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤采缚,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后挠他,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體扳抽,經(jīng)...
    沈念sama閱讀 45,693評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,885評(píng)論 3 336
  • 正文 我和宋清朗相戀三年绩社,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了摔蓝。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,001評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡愉耙,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出拌滋,到底是詐尸還是另有隱情朴沿,我是刑警寧澤,帶...
    沈念sama閱讀 35,723評(píng)論 5 346
  • 正文 年R本政府宣布,位于F島的核電站赌渣,受9級(jí)特大地震影響魏铅,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜坚芜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,343評(píng)論 3 330
  • 文/蒙蒙 一览芳、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧鸿竖,春花似錦沧竟、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,919評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至闪水,卻和暖如春糕非,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背球榆。 一陣腳步聲響...
    開封第一講書人閱讀 33,042評(píng)論 1 270
  • 我被黑心中介騙來泰國(guó)打工朽肥, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人持钉。 一個(gè)月前我還...
    沈念sama閱讀 48,191評(píng)論 3 370
  • 正文 我出身青樓衡招,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親右钾。 傳聞我的和親對(duì)象是個(gè)殘疾皇子蚁吝,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,955評(píng)論 2 355

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