[英]Is my Double-Checked Locking Pattern implementation right?
Meyers的書“ Effective Modern C ++ ”第16項中的一個例子。
在一個緩存昂貴的計算int的類中,您可能會嘗試使用一對std :: atomic avriable而不是互斥鎖:
class Widget {
public:
int magicValue() const {
if (cachedValid) {
return cachedValue;
} else {
auto val1 = expensiveComputation1();
auto val2 = expensiveComputation2();
cachedValue = va1 + val2;
cacheValid = true;
return cachedValue;
}
}
private:
mutable std::atomic<bool> cacheValid { false };
mutable std::atomic<int> cachedValue;
};
這樣可以工作,但有時它會比它應該工作得多。考慮:一個線程調用Widget :: magicValue,將cacheValid視為false,執行兩個昂貴的計算,並將它們的總和分配給cachedValud。 此時,第二個線程calidget Widget :: magicValue也將cacheValid視為false,因此執行與第一個線程剛完成相同的昂貴計算。
然后他用互斥量給出了一個解決方案:
class Widget {
public:
int magicValue() const {
std::lock_guard<std::mutex> guard(m);
if (cacheValid) {
return cachedValue;
} else {
auto val1 = expensiveComputation1();
auto val2 = expensiveComputation2();
cachedValue = va1 + val2;
cacheValid = true;
return cachedValue;
}
}
private:
mutable std::mutex m;
mutable bool cacheValid { false };
mutable int cachedValue;
};
但我認為解決方案不是那么有效,我認為將mutex和atomic結合起來組成一個Double-Checked鎖定模式 ,如下所示。
class Widget {
public:
int magicValue() const {
if (!cacheValid) {
std::lock_guard<std::mutex> guard(m);
if (!cacheValid) {
auto val1 = expensiveComputation1();
auto val2 = expensiveComputation2();
cachedValue = va1 + val2;
cacheValid = true;
}
}
return cachedValue;
}
private:
mutable std::mutex m;
mutable std::atomic<bool> cacheValid { false };
mutable std::atomic<int> cachedValue;
};
因為我是多線程編程的新手,所以我想知道:
編輯:
修正了代碼。 if(!cachedValue) - > if(!cacheValid)
正如HappyCactus所指出的,第二次檢查if (!cachedValue)
實際上應該是if (!cachedValid)
。 除了這個錯字,我認為你的雙重鎖定模式的演示是正確的。 但是,我認為沒有必要在cachedValue
上使用std::atomic
。 寫入cachedValue
的唯一地方是cachedValue = va1 + val2;
。 在它完成之前,任何線程都不會到達語句return cachedValue;
這是唯一一個讀取cachedValue
的地方。 因此,寫入和讀取不可能是並發的。 並發讀取沒有問題。
您可以通過減少內存排序要求來提高解決方案的效率。 此處不需要原子操作的默認順序一致性內存順序。
在x86上性能差異可以忽略不計,但在ARM上卻很明顯,因為ARM上的順序一致性內存順序很昂貴。 有關詳細信息,請參閱Herb Sutter的“強”和“弱”硬件內存模型 。
建議的更改:
class Widget {
public:
int magicValue() const {
if (cachedValid.load(std::memory_order_acquire)) { // Acquire semantics.
return cachedValue;
} else {
auto val1 = expensiveComputation1();
auto val2 = expensiveComputation2();
cachedValue = va1 + val2; // Non-atomic write.
// Release semantics.
// Prevents compiler and CPU store reordering.
// Makes this and preceding stores by this thread visible to other threads.
cachedValid.store(true, std::memory_order_release);
return cachedValue;
}
}
private:
mutable std::atomic<bool> cacheValid { false };
mutable int cachedValue; // Non-atomic.
};
我的代碼是對的嗎?
是。 您應用雙重鎖定模式是正確的。 但請參閱下面的一些改進。
它的表現更好嗎?
與完全鎖定的變體(在你的帖子中為第2個)相比,它通常具有更好的性能,直到magicValue()
僅被調用一次(但即使在那種情況下,性能損失也可以忽略不計)。
與無鎖變體(你的帖子中的第一個)相比,你的代碼表現出更好的性能,直到值計算比等待互斥鎖更快。
例如, 10個值的總和 (通常)比等待互斥鎖的 速度快 。 在那種情況下,第一種變體是可取的。 另一方面, 10個文件讀取比等待互斥鎖 慢 ,所以你的變體比1st更好。
實際上,您的代碼有一些簡單的改進,這使得它更快(至少在某些機器上)並提高代碼的理解:
cachedValue
變量根本不需要原子語義。 它受cacheValid
標志的保護,該原子性完成所有工作。 而且,單原子標志可以保護幾個非原子值。
另外,如回答https://stackoverflow.com/a/30049946/3440745所述 ,當訪問cacheValid
標志時,您不需要順序一致性順序(當您只是讀取或寫入原子變量時默認應用順序),發布 - 獲取訂單就足夠了。
class Widget {
public:
int magicValue() const {
//'Acquire' semantic when read flag.
if (!cacheValid.load(std::memory_order_acquire)) {
std::lock_guard<std::mutex> guard(m);
// Reading flag under mutex locked doesn't require any memory order.
if (!cacheValid.load(std::memory_order_relaxed)) {
auto val1 = expensiveComputation1();
auto val2 = expensiveComputation2();
cachedValue = va1 + val2;
// 'Release' semantic when write flag
cacheValid.store(true, std::memory_order_release);
}
}
return cachedValue;
}
private:
mutable std::mutex m;
mutable std::atomic<bool> cacheValid { false };
mutable int cachedValue; // Atomic isn't needed here.
};
這是不正確的:
int magicValue() const {
if (!cachedValid) {
// this part is unprotected, what if a second thread evaluates
// the previous test when this first is here? it behaves
// exactly like in the first example.
std::lock_guard<std::mutex> guard(m);
if (!cachedValue) {
auto val1 = expensiveComputation1();
auto val2 = expensiveComputation2();
cachedValue = va1 + val2;
cachedValid = true;
}
}
return cachedValue;
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.