我可以制作一个线程安全的 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<int>
。
因此,经过 1000 个周期后,我有一个 1000 个int
s 的向量,可以通过统计数据进行检查。
由于std::vector
不是线程安全的,我想到了std::mutex
(肯定会工作)。
但我想知道我是否可以将向量声明为原子的,从而绕过互斥锁?
是否有可能拥有std::atomic<std::vector<int>>
?我可以在上面使用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<std::vector<int>>
。
由于您事先知道大小,并且您不需要使用需要将向量重新分配到不同位置的方法(例如push_back)
。您可以使用std::vector<int>::resize
或大小构造函数来预分配和预构造所需的int
s。因此,您的并发线程不需要对向量本身进行操作,而是对元素进行操作。
如果不同线程无法访问同一元素,则不存在竞争条件。
int k[1000]
也是如此,它可以简单地复制。但您不需要这样做,因为线程不会更改数组/向量/列表本身,而是更改元素。
【讨论】:
【参考方案2】:你不需要。从多个线程访问std::vector
完全没问题,如果
因此,请确保您创建了一个大小为 n=1000
的向量,并根据您的线程编号(1 到 4)为您的线程分配元素 0-249、250-499 等。
因此,您的每个线程都会计算 n/nthreads
元素。
【讨论】:
在将向量的一部分分配给线程之前,适当调整向量的大小非常重要——任何调整大小都绝对不是线程安全的。 但是即使你这样做,你仍然需要在阅读器和编写器之间进行同步。特别是,但不仅如此,因为不能保证对int
的访问在所有架构上都是原子的,除非您使用 atomic_int
或 std::atomic<int>
。【参考方案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 一起使用吗