我的多线程代码有 3 个不同的部分,可以这样描述:
(1)不受互斥锁保护: 这部分代码仅对 local 和 thread_local 变量以及保证在多线程期间不会更改的全局变量进行工作。
(2)受shared_lock保护: 这部分可以访问但不能修改一堆全局数据结构
(3)受unique_lock保护: 这部分可以访问和修改(有时是破坏性的)一堆全局数据结构
我只有一个全局shared_mutex,它被锁锁定了。
一切正常,但我注意到我有很多地方看起来像这样
struct MyStruct {
uint64_t counter;
//some other fields
};
unordered_map<uint64_t, MyStruct> map;
shared_mutex mutex;
void someFunction(uint64_t id) {
shared_lock lock(mutex);
auto it = map.find(id);
if(it == map.end()) return;
MyStruct &s = it->second;
uint64_t counterValue = s.counter;
doSomeWork(counterValue);//old value of counter is used
mutex.unlock_shared();
mutex.lock();
//if we don't find id in map, it means that s was deleted during lock transition
//id is never reused for a different struct
if(map.find(id) == map.end()) return;
s.counter += 1;
mutex.unlock();
mutex.lock_shared();
if(map.find(id) == map.end()) return;
doSomeMoreWork();//counter is not used anymore
}
进行大量重新锁定来增加单个计数器似乎很浪费,所以我想将计数器更改为原子计数器并具有类似的内容:
struct MyStruct {
atomic<uint64_t> counter;
//some other fields
};
unordered_map<uint64_t, MyStruct> map;
shared_mutex mutex;
void someFunction(uint64_t id) {
shared_lock lock(mutex);
auto it = map.find(id);
if(it == map.end()) return;
MyStruct &s = it->second;
uint64_t counterValue = s.counter.fetch_add(1);
doSomeWork(counterValue);//old value of counter is used
doSomeMoreWork();//counter is not used anymore
}
我没有原子方面的经验。这能正常工作吗?是否存在一些需要注意的潜在问题?我还可以为 fetch_add 提供一堆不同的内存顺序:
memory_order_relaxed
memory_order_consume
memory_order_acquire
memory_order_release
memory_order_acq_rel
memory_order_seq_cst
这里哪一个才是正确的?
是的,您可以使用原子来实现此目的。 事实上,您甚至可以将
s.counter += 1
的旧语法保留为 std::atomic<std::uint64_t>
。
但是,这将使用 std::memory_order::seq_cst
,这可能超出您的需要。
如果您唯一需要的是一个以原子方式递增的计数器,那么使用
std::memory_order::relaxed
就足够了。
但是,如有疑问,请选择 seq_cst
。
您需要哪种内存顺序取决于对原子执行的其他操作。
这始终是大局,您无法孤立地为一项操作做出决定。