C++11 原子容器的线程安全
C++11 Thread Safety of Atomic Containers
我正在尝试实现一个没有互斥体的线程安全 STL 向量。所以我遵循 this
post 并为原子原语实现了一个包装器。
然而,当我运行下面的代码时,它从下面的代码中显示了 Failed!
两次(只有两个竞争条件实例)所以它似乎不是线程安全的。我想知道我该如何解决这个问题?
包装类
template<typename T>
struct AtomicVariable
{
std::atomic<T> atomic;
AtomicVariable() : atomic(T()) {}
explicit AtomicVariable(T const& v) : atomic(v) {}
explicit AtomicVariable(std::atomic<T> const& a) : atomic(a.load()) {}
AtomicVariable(AtomicVariable const&other) :
atomic(other.atomic.load()) {}
inline AtomicVariable& operator=(AtomicVariable const &rhs) {
atomic.store(rhs.atomic.load());
return *this;
}
inline AtomicVariable& operator+=(AtomicVariable const &rhs) {
atomic.store(rhs.atomic.load() + atomic.load());
return *this;
}
inline bool operator!=(AtomicVariable const &rhs) {
return !(atomic.load() == rhs.atomic.load());
}
};
typedef AtomicVariable<int> AtomicInt;
函数和测试
// Vector of 100 elements.
vector<AtomicInt> common(100, AtomicInt(0));
void add10(vector<AtomicInt> ¶m){
for (vector<AtomicInt>::iterator it = param.begin();
it != param.end(); ++it){
*it += AtomicInt(10);
}
}
void add100(vector<AtomicInt> ¶m){
for (vector<AtomicInt>::iterator it = param.begin();
it != param.end(); ++it){
*it += AtomicInt(100);
}
}
void doParallelProcessing(){
// Create threads
std::thread t1(add10, std::ref(common));
std::thread t2(add100, std::ref(common));
// Join 'em
t1.join();
t2.join();
// Print vector again
for (vector<AtomicInt>::iterator it = common.begin();
it != common.end(); ++it){
if (*it != AtomicInt(110)){
cout << "Failed!" << endl;
}
}
}
int main(int argc, char *argv[]) {
// Just for testing purposes
for (int i = 0; i < 100000; i++){
// Reset vector
common.clear();
common.resize(100, AtomicInt(0));
doParallelProcessing();
}
}
有原子容器这种东西吗?我还用常规 vector<int>
测试了它,它没有任何 Failed
输出,但这可能只是巧合。
请注意
atomic.store(rhs.atomic.load() + atomic.load());
是不是原子的
你有两种选择来解决它。
回忆
1) 使用互斥量。
EDIT 正如评论中提到的 T.C 这是无关紧要的,因为这里的操作将是 load() 然后 load() 然后 store() (不是放松模式) - 所以内存顺序在这里不相关。
2) 使用内存顺序http://bartoszmilewski.com/2008/12/01/c-atomics-and-memory-ordering/
memory_order_acquire: guarantees that subsequent loads are not moved before the current load or any preceding loads.
memory_order_release: preceding stores are not moved past the current store or any subsequent stores.
我仍然不确定 2,但我认为如果商店不并行,它会起作用。
只需将运算符+=写为:
inline AtomicVariable& operator+=(AtomicVariable const &rhs) {
atomic += rhs.atomic;
return *this;
}
在文档中:http://en.cppreference.com/w/cpp/atomic/atomic 运算符 += 是原子的。
您的示例失败,因为以下执行场景是可能的:
- 线程 1 - rhs.atomic.load() - returns 10 ;线程 2 - rhs.atomic.load() - returns 100
- 线程 1 - atomic.load() - returns 0 ;线程 2 - atomic.load - returns 0
- Thread1 - 添加值 (0 + 10 = 10) ; Thread2 - 添加值 (0 + 100)
- 线程 1 - atomic.store(10);线程 2 - atomic.store(100)
最终在这种情况下,原子值可能是 10 或 100,取决于哪个线程首先执行 atomic.store。
我正在尝试实现一个没有互斥体的线程安全 STL 向量。所以我遵循 this
post 并为原子原语实现了一个包装器。
然而,当我运行下面的代码时,它从下面的代码中显示了 Failed!
两次(只有两个竞争条件实例)所以它似乎不是线程安全的。我想知道我该如何解决这个问题?
包装类
template<typename T>
struct AtomicVariable
{
std::atomic<T> atomic;
AtomicVariable() : atomic(T()) {}
explicit AtomicVariable(T const& v) : atomic(v) {}
explicit AtomicVariable(std::atomic<T> const& a) : atomic(a.load()) {}
AtomicVariable(AtomicVariable const&other) :
atomic(other.atomic.load()) {}
inline AtomicVariable& operator=(AtomicVariable const &rhs) {
atomic.store(rhs.atomic.load());
return *this;
}
inline AtomicVariable& operator+=(AtomicVariable const &rhs) {
atomic.store(rhs.atomic.load() + atomic.load());
return *this;
}
inline bool operator!=(AtomicVariable const &rhs) {
return !(atomic.load() == rhs.atomic.load());
}
};
typedef AtomicVariable<int> AtomicInt;
函数和测试
// Vector of 100 elements.
vector<AtomicInt> common(100, AtomicInt(0));
void add10(vector<AtomicInt> ¶m){
for (vector<AtomicInt>::iterator it = param.begin();
it != param.end(); ++it){
*it += AtomicInt(10);
}
}
void add100(vector<AtomicInt> ¶m){
for (vector<AtomicInt>::iterator it = param.begin();
it != param.end(); ++it){
*it += AtomicInt(100);
}
}
void doParallelProcessing(){
// Create threads
std::thread t1(add10, std::ref(common));
std::thread t2(add100, std::ref(common));
// Join 'em
t1.join();
t2.join();
// Print vector again
for (vector<AtomicInt>::iterator it = common.begin();
it != common.end(); ++it){
if (*it != AtomicInt(110)){
cout << "Failed!" << endl;
}
}
}
int main(int argc, char *argv[]) {
// Just for testing purposes
for (int i = 0; i < 100000; i++){
// Reset vector
common.clear();
common.resize(100, AtomicInt(0));
doParallelProcessing();
}
}
有原子容器这种东西吗?我还用常规 vector<int>
测试了它,它没有任何 Failed
输出,但这可能只是巧合。
请注意
atomic.store(rhs.atomic.load() + atomic.load());
是不是原子的
你有两种选择来解决它。 回忆 1) 使用互斥量。
EDIT 正如评论中提到的 T.C 这是无关紧要的,因为这里的操作将是 load() 然后 load() 然后 store() (不是放松模式) - 所以内存顺序在这里不相关。
2) 使用内存顺序http://bartoszmilewski.com/2008/12/01/c-atomics-and-memory-ordering/
memory_order_acquire: guarantees that subsequent loads are not moved before the current load or any preceding loads. memory_order_release: preceding stores are not moved past the current store or any subsequent stores.
我仍然不确定 2,但我认为如果商店不并行,它会起作用。
只需将运算符+=写为:
inline AtomicVariable& operator+=(AtomicVariable const &rhs) {
atomic += rhs.atomic;
return *this;
}
在文档中:http://en.cppreference.com/w/cpp/atomic/atomic 运算符 += 是原子的。
您的示例失败,因为以下执行场景是可能的:
- 线程 1 - rhs.atomic.load() - returns 10 ;线程 2 - rhs.atomic.load() - returns 100
- 线程 1 - atomic.load() - returns 0 ;线程 2 - atomic.load - returns 0
- Thread1 - 添加值 (0 + 10 = 10) ; Thread2 - 添加值 (0 + 100)
- 线程 1 - atomic.store(10);线程 2 - atomic.store(100)
最终在这种情况下,原子值可能是 10 或 100,取决于哪个线程首先执行 atomic.store。