c ++ - 原子的矢量完全线程安全吗?

时间:2017-08-21 09:16:24

标签: c++ vector concurrency atomic

我有一个std::vector<std::atomic<size_t>> vec。运行vec[index].fetch_add(1, std::memory_order_release)或存储/加载多个并发线程是否安全?我认为它应该是,因为读取是线程安全的,并且由于原子性而不可能从多个线程同时写入一个条目 - 是吗?

4 个答案:

答案 0 :(得分:6)

通常情况下,它不是线程安全的,因为容器本身不是原子的。

那就是说,只要你不改变向量中的内容(即做任何使data()的回归无效的事情),你就没事了。

遗憾的是,由于std::atomic<std::vector<...>>不是可以轻易复制,因此无法诉诸std::vector

答案 1 :(得分:3)

你的表达式vec[n].atomic_op(...)本身并不是原子的,而是分解为:

auto iter = vec.begin();
iter += n;
iter->atomic_op(...);

因此,前两个语句容易受到通常的迭代器失效规则的影响。同时改变向量的大小,或删除第n个或第n个元素本身之前的任何元素,都可以破坏它们。

一旦拥有元素的迭代器, if 迭代器在你使用它的时候没有失效,然后无论你对元素本身执行的任何原子操作都是安全的。

答案 2 :(得分:3)

只要您修改每个原子

vec[X].fetch_add就是安全的。如果调用包装std::vector的任何非const(修改)方法,则这是未定义的行为,因为std::vector本身不是线程安全的。

事实上,你可以在某个线程中初始化向量,将其引用传递给某个异步任务,从那时起,只对向量的特定元素起作用,而不是对向量本身起作用。 vec[X].action(...)是线程安全的,vec.action(...)不是。

答案 3 :(得分:2)

这取决于其他线程正在做什么。

来自标准:

  

17.6.5.9避免数据竞争[res.on.data.races]

     

...

     

2 C ++标准库函数不应直接或间接访问线程可访问的对象(1.10)   除了当前线程以外,除非通过函数的参数直接或间接访问对象,   包括这个。

     

3 C ++标准库函数不应直接或间接修改线程可访问的对象(1.10)   除了当前线程之外,除非通过函数的非const直接或间接访问对象   争论,包括这个。

  

23.2.2容器数据竞赛[container.requirements.dataraces]

     

1为避免数据争用(17.6.5.9),实现应考虑以下功能   const:begin,end,rbegin,rend,front,back,data,find,lower_bound,upper_bound,equal_range,at   并且,除了在关联或无序的关联容器中,运算符[]。

     

2尽管如此(17.6.5.9),还需要实现以避免数据争用时所包含的内容   除了vector&lt; bool&gt;之外,同一容器中不同元素中的对象会同时修改。

通过将这两个部分与原子的规则结合起来,我们可以推断出调用vec[index].fetch_add(1, std::memory_order_release)不会导致与其他线程执行相同或其他“const”操作的竞争条件(包括第23.2.2.1段中提到的那些) )。但是,如果另一个线程在vec本身上调用非const操作(例如inserteraseresize等等,那么我们会遇到1.10节所规定的未定义行为:

  

1.10多线程执行和数据竞赛[intro.multithread]

     

...

     

4如果其中一个修改了内存位置(1.7)而另一个访问,则两个表达式评估会发生冲突   或修改相同的内存位置。

     

...

     

21如果程序的执行在不同的线程中包含两个冲突的动作,则它包含数据竞争,   其中至少有一个不是原子的,也不会发生在另一个之前。任何此类数据竞赛都会产生   未定义的行为。