2017-06-04 28 views
1

考慮下面的類:什麼時候getter和setter與互斥體是線程安全的?

class testThreads 
{ 
private: 
    int var; // variable to be modified 
    std::mutex mtx; // mutex 
public: 
    void set_var(int arg) // setter 
    { 
     std::lock_guard<std::mutex> lk(mtx); 
     var = arg; 
    } 

    int get_var() // getter 
    { 
     std::lock_guard<std::mutex> lk(mtx); 
     return var; 
    } 

    void hundred_adder() 
    { 
     for(int i = 0; i < 100; i++) 
     { 
      int got = get_var(); 
      set_var(got + 1); 
      sleep(0.1); 
     } 
    } 
}; 

當我創建在主)兩個線程(,每個具有hundred_adder修改相同的變量var線程功能,變種的最終結果是始終不同,即不是200,但一些其他的號碼。

從概念上講,爲什麼這種使用互斥體的getter和setter函數不是線程安全的?防守隊員是否無法阻止比賽狀況發生變化?什麼是替代解決方案?

+3

A-get,B-get,A-set,B-set。 :( –

回答

4
Thread a: get 0 
Thread b: get 0 
Thread a: set 1 
Thread b: set 1 

瞧,VAR爲1即使它應該已經2

很明顯,你需要鎖定整個操作:

for(int i = 0; i < 100; i++){ 
    std::lock_guard<std::mutex> lk(mtx); 
    var += 1; 
} 

另外,您可以使可變原子(即使是輕鬆的可以在你的情況下)。

2

該代碼以線程安全的方式顯示它永遠不會設置或獲取變量的部分值。

但是,您對這些方法的使用並不能保證值會正確更改:從多個線程讀取和寫入可能會相互衝突。這兩個線程讀取值(11),都將其遞增(至12),並且都設置爲相同(12) - 現在您計算了2,但實際上僅遞增了一次。

選項來解決:

  • 提供「安全增值」操作
  • 提供的InterlockedCompareExchange相當於讓你更新對應於原來的一個肯定值,並根據需要
  • 包裝調用代碼重試成獨立互斥或使用其他同步機制來防止操作混合。
3
int got = get_var(); 
    set_var(got + 1); 

get_var()set_var()本身是線程安全的。但是這個get_var()之後的set_var()的組合序列不是。沒有互斥體可以保護整個序列。

您有多個執行此操作的併發線程。你有多個線程調用get_var()。在第一個線程完成並解除互斥鎖後,另一個線程可以立即鎖定互斥鎖,並獲得與第一個線程所做的got相同的值。絕對沒有什麼能夠阻止多線程同時鎖定並獲得相同的got

然後兩個線程都會調用set_var(),將互斥保護的int更新爲相同的值。

這只是在這裏可能發生的一種可能性。你可以很容易地有多個線程順序獲取互斥鎖,從而使var增加了幾個值,只是後面跟着一些其他的停滯線程,幾秒鐘前稱爲get_var(),而現在只是調用set_var(),因此將var重置爲價值小得多。

+1

...這就是爲什麼線程安全不是_composable_。即,建立一個程序/庫/類/完全不是線程安全的部分不會使整個線程安全的 –

0

爲什麼你不使用std :: atomic作爲共享數據(在這種情況下是var)?這將更安全高效。

+0

...也許,但那麼他永遠不會知道他的問題的答案。 –

0

這是一個絕對的經典。 一個線程獲得var的值,釋放mutex,另一個線程在第一個線程有機會更新它之前獲得相同的值。

因此,這個過程有可能失去增量。

有三個明顯的解決方案:

void testThreads::inc_var(){ 
    std::lock_guard<std::mutex> lk(mtx); 
    ++var; 
} 

,由於互斥量一直保持到變量更新是安全的。

下一步:

bool testThreads::compare_and_inc_var(int val){ 
    std::lock_guard<std::mutex> lk(mtx); 
    if(var!=val) return false; 
    ++var; 
    return true; 
} 

Then write code like: 

    int val; 
    do{ 
     val=get_var(); 
    }while(!compare_and_inc_var(val)); 

這工作,因爲循環重複,直到它證實它的更新它讀取的值。這可能會導致實時鎖定,但在這種情況下,它必須是暫時的,因爲線程只能失敗,因爲另一個會執行。

最後替換int varstd::atomic<int> var,要麼使用++varvar.compare_exchange(val,val+1)var.fetch_add(1);進行更新。 注:請注意compare_exchange(var,var+1)無效...

++保證是原子上std::atomic<>類型,但儘管「看」像一般的單一操作沒有這樣的保證存在int

std::atomic<>也提供了適當的內存屏障(以及提示需要什麼樣的屏障的方法)以確保正確的線程間通信。

std::atomic<>在可用的情況下應該是一個免等待的無鎖實現。檢查你的文件和國旗is_lock_free()

相關問題