2010-07-08 97 views
13

考慮下面的C++成員函數:同步訪問返回值

size_t size() const 
{ 
    boost::lock_guard<boost::mutex> lock(m_mutex); 
    return m_size; 
} 

這裏的目的不是同步訪問私有成員變量m_size,但只是爲了確保致電者收到有效值爲m_size。目標是防止函數返回m_size,同時某個其他線程正在修改m_size

但是在調用這個函數時是否有任何潛在的種族條件?我不確定這裏的RAII風格鎖是否足以抵禦競爭條件。假設鎖的析構函數在之前被稱爲,函數的返回值被壓入棧中?

我需要像下面這樣來保證線程安全嗎?

size_t size() const 
{ 
    size_t ret; 

    { 
     boost::lock_guard<boost::mutex> lock(m_mutex); 
     ret = m_size; 
    } 

    return ret; 
} 
+0

我不認爲你的鎖真的做任何事情。只要'm_size'可以在單個原子操作中完整讀取,就會得到一個有效的值。 – 2010-07-08 01:29:46

+0

這實際上並不能保證。然而,使用C++ 0x附帶的'std :: atomic'可以確保不鎖定。 – 2010-07-08 06:38:57

+0

@Mike:鎖也是一種記憶障礙。這需要同步處理器緩存等。 – sbi 2010-07-08 09:08:56

回答

12

無論你的榜樣的建會做你要找的內容。從標準以下信息支持你正在尋找(甚至在你的第一個例子)的行爲:

12.4/10解構:

析構函數裏隱含調用......與自動構造的對象存儲持續時間(3.7.2)時,創建對象的塊將退出。

而且,6.6/2跳轉語句(其中return是其中之一):

在從一個範圍(但是完成),析構函數(12.4)被稱爲用於與自動存儲所有構造的對象出口持續時間(3.7.2)(命名對象或臨時對象)在該範圍內聲明,按其聲明的相反順序。從循環中移出,從塊中移出,或者通過具有自動存儲持續時間的初始化變量返回,會涉及銷燬具有自動存儲持續時間的變量,這些變量的範圍位於從傳輸點傳輸但不在傳輸點處的範圍。

因此,在return這一點,lock變量在範圍內,因此dtor沒有被調用。一旦執行return,調用lock變量的dtor(從而釋放鎖)。

1

您的初始代碼很好 - 析構函數將在返回值被存儲後調用。這是RAII運作的原則!

+3

您有任何消息來源嗎? – 2010-07-08 01:12:07

3

您的第一個變體是安全的,但是您不能依賴此返回的值在任何時間段內保持一致。我的意思是,例如,不要在for循環中使用返回的值來迭代每個元素,因爲實際大小在返回後可能會發生變化。

基本上你可以這樣想:返回值需要一個副本,否則析構函數會被調用,因此可能會破壞返回值返回之前的任何值。

析構函數在return語句後調用。 藉此相當於例如:

#include <assert.h> 

class A 
{ 
public: 
    ~A() 
    { 
     x = 10; 
    } 
    int x; 
}; 

int test() 
{ 
    A a; 
    a.x = 5; 
    return a.x; 
} 

int main(int argc, char* argv[]) 
{ 
    int x = test(); 
    assert(x == 5); 
    return 0; 
} 
+0

想到這件事後,我發現這是行爲可行的唯一方式。每個'return expr;'隱式地將'expr'的值複製到一個臨時對象中,並且範圍內的任何對象都可以在'expr'中引用,因此它們必須在複製時仍處於活動狀態。 (優化可以刪除副本,但行爲必須仍然「好像」在那裏)。 – 2010-07-08 01:28:46