C++多線程學習---線程間的共用資料

來源:互聯網
上載者:User

多線程間的共用資料如果不加以約束是有問題的。最簡單的方法就是對資料結構採用某種保護機制,通俗的表達就是:

確保只有進行修改的線程才能看到不變數被破壞時的中間狀態。從其他訪問線程的角度來看,修改不是已經完成了,就是還沒開始。

1.使用互斥量保護共用資料

    當訪問共用資料前,使用互斥量將相關資料鎖住,再當訪問結束後,再將資料解鎖。C++標準庫為互斥量提供了一個RAII文法的模板類std::lack_guard ,其會在構造的時候提供已鎖的互斥量,並在析構的時候進行解鎖.

#include <list>#include <mutex>#include <algorithm>std::list<int> some_list; // 1std::mutex some_mutex; // 2void add_to_list(int new_value){    std::lock_guard<std::mutex> guard(some_mutex); // 3    some_list.push_back(new_value);}bool list_contains(int value_to_find){    std::lock_guard<std::mutex> guard(some_mutex); // 4    return std::find(some_list.begin(),some_list.end(),value_to_find) != some_list.end();}
上述例子通過lock_guart<std::mutex>使得add_to_list和list_contains這兩個函數對資料的訪問是互斥的。從而保證多線程中資料的安全。


2.堤防介面內在的條件競爭

比如你在寫一個棧的資料結構,並且給棧的push\pop\top\empty\size\等介面增加了互斥保護。看以下代碼:

stack<int> s;if (! s.empty()){ // 1int const value = s.top(); // 2s.pop(); // 3do_something(value);}
以上只是單安全執行緒,對於多線程在1和2之間,可能有來自另一個線程的pop()調用並刪除了最後一個元素,此時就會出問題。

因為鎖的粒度太小,需要保護的操作並未全覆蓋到。可以考慮適當增大鎖的粒度。

std::shared_ptr<T> pop(){    std::lock_guard<std::mutex> lock(m);    if(data.empty()) throw empty_stack(); // 在調用pop前,檢查棧是否為空白    std::shared_ptr<T> const res(std::make_shared<T>(data.top())); // 在修改堆棧前,分配出傳回值    data.pop();return res;}void pop(T& value){    std::lock_guard<std::mutex> lock(m);    if(data.empty()) throw empty_stack();    value=data.top();    data.pop();}

3.堤防死結問題

造成死結最大的問題是:由兩個或兩個以上的互斥量來鎖定一個操作。一對線程需要對他們所有的互斥量做一些操作,其中每個線程都有一個互斥量,且等待另一個解鎖。這樣沒有線程能工作,因為他們都在等待對方釋放互斥量。

避免死結的一般建議,就是讓兩個互斥量總以相同的順序上鎖:總在互斥量B之前鎖住互斥量A,可以有效防止大部分問題。一個更好的方法是利用C++提供的std::lock,可以一次性鎖住多個互斥量。看以下例子:

class some_big_object;void swap(some_big_object& lhs,some_big_object& rhs);class X{private:    some_big_object some_detail;    std::mutex m;public:    X(some_big_object const& sd):some_detail(sd){}    friend void swap(X& lhs, X& rhs)    {        if(&lhs==&rhs)      return;        std::lock(lhs.m,rhs.m); // 1        std::lock_guard<std::mutex> lock_a(lhs.m,std::adopt_lock); // 2        std::lock_guard<std::mutex> lock_b(rhs.m,std::adopt_lock); // 3        swap(lhs.some_detail,rhs.some_detail);     }};
調用std::lock() ①鎖住兩個互斥量,並且兩個std:lock_guard 執行個體已經建立好②③,還有一個互斥量。提供std::adopt_lock 參數除了表示std::lock_guard 對象已經上鎖外,還表示現成的鎖,而非嘗試建立新的鎖。

注意:一個互斥量可以在同一線程上多次上鎖(std::recursive_mutex)。

以下是設計鎖的幾條忠告:

1.盡量避免嵌套鎖,一個線程已獲得鎖時別再擷取第二個

2.避免在持有鎖是調用使用者提供的代碼,因為使用者的代碼不可預知,有發生死結的可能

3.使用固定順序擷取鎖--多於多個鎖時可有效避免死結

對於鎖的粒度問題也是需要注意的,大的粒度可以保護更多的資料,但是其對效能的影響也越大。同時需要儘可能他將鎖的持有時間減到最小。

比較操作符一次鎖住一個互斥量:

 
friend bool operator==(Y const& lhs, Y const& rhs){
    if(&lhs==&rhs)        return true;    int const lhs_value=lhs.get_detail(); // 2    int const rhs_value=rhs.get_detail(); // 3    return lhs_value==rhs_value; // 4}


 

 

利用int的拷貝來減少鎖的等待時間,是一種高效的做法。

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.