<div>
函数class Test {
struct hazard_pointer {
std::atomic<void*> hp;
std::atomic<std::thread::id> id;
};
hazard_pointer hazard_pointers[max_hazard_pointers];
std::atomic<void*>& get_hazard_pointer_for_current_thread(){
std::thread::id id = std::this_thread::get_id();
for( int i =0; i < max_hazard_pointers; i++){
if( hazard_pointers[i].id.load() == id){
hazard_pointers[i].id.store(id);
return hazard_pointers[i].hp;
}
}
std::atomic<nullptr> ptr;
return ptr;
}
};
int main() {
Test* t =new Test();
std::thread t1([&t](){ while(1) t->get_hazard_pointer_for_current_thread();});
std::thread t2([&t](){ while(1) t->get_hazard_pointer_for_current_thread();});
t1.join();
t2.join();
return 0;
}
可以并行执行。有get_hazard_pointer_for_current_thread
吗?由于原子操作,我的眼睛没有data race
,但我不确定。
所以,请确认或解释为什么会有(数据)数据竞赛。
我们假设data race
数组元素已初始化。
答案 0 :(得分:2)
代码中有一些错误:
get_hazard_pointer_for_current_thread
可能不会返回任何值 - 未定义的行为。hazard_pointers
数组元素未初始化。if(hazard_pointers[i].id.load() == id) hazard_pointers[i].id.store(id);
没有任何意义。是的,有数据竞争。在语句if(hazard_pointers[i].id.load() == id)
和hazard_pointers[i].id.store(id);
之间,另一个帖子可能会更改hazard_pointers[i].id
。您可能需要使用比较和交换指令。
答案 1 :(得分:1)
我不认为您可以通过并发访问非原子数据来获得任何C ++ UB,但您的代码中似乎有the normal kind of race condition。
if (x==a) x = b
几乎总是需要在无锁算法中进行原子读 - 修改 - 写(而不是单独的原子加载和原子库),除非出现这样的原因。如果b
在支票和商店之间更改为x
以外的其他内容,则仍然可以存储a
。
(在这种情况下,唯一可以存储的是已经存在的值,正如@MargaretBloom指出的那样。所以没有&#34; bug&#34;,只是一堆无用的商店,如果这是唯一接触数组的代码。我假设你并没有打算写一个无用的例子,所以我认为这是一个错误。)
无锁编程并不容易,即使您使用默认std::memory_order_seq_cst
的低性能方式对所有存储执行此操作,因此编译器到处都必须MFENCE
。使所有atomic
只能避免使用C ++ UB;您仍然必须仔细设计算法的逻辑,以确保它是正确的,即使来自其他线程的多个存储/加载在您自己的每个操作之间变得可见,以及类似的东西。 (例如,请参阅Preshing's lock-free hash table。)
无UB是必要的(至少在理论上),但绝对不足以使代码正确/安全。无比赛意味着即使在原子访问之间也没有(有问题的)比赛。这是一个更强大但仍然不足以避免无错误的部分。
我说&#34;理论上&#34;因为在实践中,很多带UB的代码都按照我们期望的方式进行编译,并且只会在其他平台或未来的编译器上,或者在优化期间暴露UB的不同周围代码中咬你。
测试无法轻易检测到所有错误,尤其是如果您只测试强烈排序的x86硬件,但是这样的简单错误应该可以通过测试轻松检测到。
您的代码存在问题,更详细:
使用原子加载和单独的原子库进行非原子比较交换:
if( hazard_pointers[i].id.load() == id){
// a store from another thread can become visible here
hazard_pointers[i].id.store(id);
return hazard_pointers[i].hp;
}
.store()
应该是std::compare_exchange_strong
,因此如果来自其他线程的商店更改了您的商店和商店之间的价值,则不会修改该值。 (将其置于if
放松或获取负载内仍然是一个好主意;我认为如果您希望该值在大多数情况下不匹配,则避免使用lock cmpxchg
是一个好主意当没有线程在这些元素上找到匹配时,这应该让缓存行保持Shared。)