有数据竞争吗?

时间:2016-07-28 16:16:09

标签: multithreading c++11 x86

<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数组元素已初始化。

2 个答案:

答案 0 :(得分:2)

代码中有一些错误:

  1. get_hazard_pointer_for_current_thread可能不会返回任何值 - 未定义的行为。
  2. hazard_pointers数组元素未初始化。
  3. if(hazard_pointers[i].id.load() == id) hazard_pointers[i].id.store(id);没有任何意义。
  4. 是的,有数据竞争。在语句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。)