我可以制作一个线程安全的 std::atomic<vector<int>> 吗?

Posted

技术标签:

【中文标题】我可以制作一个线程安全的 std::atomic<vector<int>> 吗?【英文标题】:Can I make a thread-safe std::atomic<vector<int>>? 【发布时间】:2015-12-18 02:06:29 【问题描述】:

我有一个需要执行n=1000 次的函数。此函数执行蒙特卡洛风格模拟并返回int 作为结果。我想并行运行nthreads=4。每当一个线程完成一个循环时,它应该将结果放入std::vector&lt;int&gt;。 因此,经过 1000 个周期后,我有一个 1000 个ints 的向量,可以通过统计数据进行检查。

由于std::vector 不是线程安全的,我想到了std::mutex(肯定会工作)。

但我想知道我是否可以将向量声明为原子的,从而绕过互斥锁? 是否有可能拥有std::atomic&lt;std::vector&lt;int&gt;&gt;?我可以在上面使用push_back 等吗?

【问题讨论】:

std::atomic<:vector>> 编译了吗? 我无法在这台机器上尝试...但只是遇到了原子。 我只是想补充一点,如果您从一开始就知道您将执行 1000 次,并且您的容器将准确存储 1000 个结果,那么为什么要使用动态容器?我知道 std::vector 在其实现中使用数组,如果您在一开始就保留足够的空间,则不需要重新分配(因此使用 std::array 不会有任何性能提升)。 【参考方案1】:

C++11 §29.5/1 说

有一个通用类模板原子。模板参数 T 的类型应该是可简单复制的 (3.9)。

平凡可复制是什么意思?

§3.9 告诉

标量类型、可平凡复制的类类型(第 9 条)、此类类型的数组以及这些类型的 cv 限定版本 (3.9.3) 统称为可平凡复制类型。

对于类类型(std::vector 是):

一般可复制的类是这样的类:

没有重要的复制构造函数 没有重要的移动构造函数 没有重要的复制赋值运算符 没有重要的移动赋值运算符 有一个简单的析构函数

根据此列表,std::vector 不可轻易复制,因此您不能使用 std::atomic&lt;std::vector&lt;int&gt;&gt;

由于您事先知道大小,并且您不需要使用需要将向量重新分配到不同位置的方法(例如push_back)。您可以使用std::vector&lt;int&gt;::resize 或大小构造函数来预分配和预构造所需的ints。因此,您的并发线程不需要对向量本身进行操作,而是对元素进行操作。

如果不同线程无法访问同一元素,则不存在竞争条件。

int k[1000] 也是如此,它可以简单地复制。但您不需要这样做,因为线程不会更改数组/向量/列表本身,而是更改元素。

【讨论】:

【参考方案2】:

你不需要。从多个线程访问std::vector 完全没问题,如果

你读过对象 你写到不同的对象

因此,请确保您创建了一个大小为 n=1000 的向量,并根据您的线程编号(1 到 4)为您的线程分配元素 0-249、250-499 等。

因此,您的每个线程都会计算 n/nthreads 元素。

【讨论】:

在将向量的一部分分配给线程之前,适当调整向量的大小非常重要——任何调整大小都绝对不是线程安全的。 但是即使你这样做,你仍然需要在阅读器和编写器之间进行同步。特别是,但不仅如此,因为不能保证对 int 的访问在所有架构上都是原子的,除非您使用 atomic_intstd::atomic&lt;int&gt;【参考方案3】:

Atomic 可以用简单的可复制类型来实例化。矢量不是这样的类型。

【讨论】:

问题不是答案。请向 OP 写问题作为对问题的评论 int k[1000] 这样的 C 样式数组是“可简单复制的类型”吗? k 只是一个指针。指针很容易复制。但我不知道将其用作表是否是线程安全的,因为k 是一个普通指针,但k[5] 可以写成*(k+5)k+5 是一个不同的指针,但我不确定它真的。您可以从std 查看is_trivially_copyable。您通常会为线程安全容器编写自己的 threadsafe_vector 类型。

以上是关于我可以制作一个线程安全的 std::atomic<vector<int>> 吗?的主要内容,如果未能解决你的问题,请参考以下文章

原子增加和比较是线程安全的吗

使用原子的线程安全单例

std::atomic 可以安全地与 OpenMP 一起使用吗

C++ 线程安全和 notify_all()

使用 std::atomic<int> 索引对大型数组进行异步并行化操作有多安全

std::atomic_flag 停止多个线程