array< atomic_size_t, 10 > A;
无论是atomic_init(A,{0})
还是A = {ATOMIC_VAR_INIT(0)}
都似乎不起作用,会返回一个难以理解的错误。您如何将原子数组初始化为0?
即使使用循环逐步更新数组中的一个元素也不起作用。如果我们无法初始化数组,那么原子数组的目的是什么?
我还想补充一点,我的实际数组大小非常大(不像示例中的10),因此我需要直接初始化。
std::array<std::atomic<std::size_t>, 100> A;
for(auto&x:A)
std::atomic_init(&x,std::size_t(0));
clang++ -std=c++11 -stdlib=libc++ -Weverything -Wno-c++98-compat
我使用clang-3.3。我也尝试了gcc 4.8,但它不支持std :: atomic_init()。 然而,我认为你可以用x = std :: size_t(0)替换std :: atomic_init(&x,std :: size_t(0))。
请注意,std :: atomic <>不可复制,这会破坏一些容器方法(包括从T构造std :: array >) 。此外,在数组中存储原子可能会导致错误共享,影响性能。
编辑2019
Zac Howland的已接受的答案中的代码无法编译(无论是使用clang还是gcc)。这是一个可以编译的版本
struct foo
{
std::array<std::atomic_size_t,2> arr= {{{0},{0}}};
std::atomic_size_t arr_alt[2] = {{0},{0}};
};
std::array<atomic_size_t, 10> arr = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
或者您可以编译C++11版
std::array<std::atomic_size_t, 10> arr{{{0}, {0}, {0}, {0}, {0}, {0}, {0}, {0}, {0}, {0} }}; // double braces required
示例:https://www.ideone.com/Mj9kfE
编辑:
我刚才想到,您正在尝试将无法复制的原子存储在需要可复制的集合中(注:我目前无法获取我的标准副本。我知道其他集合都适用,但我不确定对于std::array
是否也适用)。
一段时间前曾发布过类似的问题:线程安全的无锁数组
对于实际初始化,而不是清零现有数组,一个空的初始化列表就可以正常工作,就像对于普通的int arr[N] = {};
一样,对任意大小的数组进行零初始化。
参见https://en.cppreference.com/w/cpp/language/zero_initialization - 大括号初始化列表中的省略元素会被隐式地置为零。
这也适用于静态存储,比如使用static
或在全局范围内。
#include <atomic>
#include <array>
#include <cstddef>
size_t foo(){
std::array< std::atomic<size_t>, 10 > arr {};
return arr[5].load(); // and use the array just to prove it works
}
// same asm as for a primitive array:
size_t bar(){
std::atomic_size_t arr[10] {};
return arr[5].load();
}
= {}
或{}
,如果你喜欢的话,但... arr[10] ()
看起来像函数指针。
在Godbolt上查看 - 使用-Wall -pedantic -std=c++11
或-std=c++23
从G++ 13.2或clang++ 17,无论是否使用-stdlib=libc++
,都没有警告。MSVC的-Wall
会从其标准库头文件中产生成千上万个警告,因此无法使用,但是从MSVC 19.37或19.14没有错误。Clang会优化掉数组的初始化和加载,因为它可以看到没有其他线程可能引用原子对象;你可以将其传递给一个不透明函数,以查看Clang的代码生成(如Godbolt链接中所示)。
对于事后清零,例如在某些使用之后,但我们知道没有其他线程会读取或写入,memset
在实践中可能是安全的,特别是如果它是无锁的,并且/或者 static_assert(sizeof(std::atomic<T>) == sizeof(T))
。这绝对不符合标准,但如果 std::atomic<T>
的对象表示与 T
的对象表示相匹配,那么它将起作用,这就是实践中实现无锁 std::atomic
的方式。
另一种在问题的单线程阶段中有效清零数组的选项是在普通的 size_t
数组上使用 atomic_ref
。但这意味着每个需要进行原子访问的地方都需要更多的代码,并且可能会意外地进行非原子访问而没有得到警告。希望这是 clang -O2 -fsanitize=threads
可以捕捉到的问题。
alignas( std::atomic_ref<size_t>::required_alignment ) std::array<size_t, 10> arr[10] = {};
std::fill
还是memset
都是可行的,只要您在另一个线程正在访问数组时不会创建数据竞争的未定义行为(通过atomic_ref
或其他方式)。atomic<uint8_t>
这样的较小类型来说,确实更差。)请参阅向量加载/存储和聚集/散射的每个元素的原子性? - x86手册不能保证这一点,尽管在实际硬件上几乎肯定是正确的。-std=c++11
)来运行,所以它不依赖于任何新的编译器特性。 - undefinedstd::index_sequence
来完全泛化这个问题。#include <array>
#include <atomic>
template<std::size_t N> constexpr auto make_array()
{
return []<std::size_t... Indices>(std::index_sequence<Indices...>)
{
return std::array<std::atomic_size_t, N>{(Indices-Indices)...};
}(std::make_index_sequence<N>());
}
int main()
{
auto A = make_array<10>(); //equivalent to std::array< std::atomic_size_t, 10 > A = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
}
{0,1,2,3,4,5,6,7}
一样?我知道我可以使用你的解决方案,并将Indices-Indices
替换为Indices
,但这需要C++20。 - undefined... arr {}
适用于任何原始类型(包括FP和指针),也适用于原子聚合物(https://godbolt.org/z/s7dTYcW73)。 atomic_size_t
对于除了std::index_sequence
vs. std::integer_sequence<T>
之外的任何东西都不是特别特殊。这个答案有用的地方在于它被泛化为非零init时,可以使用线性序列或者可能是左移单个位的递归,或者一些数学函数,或者其他任何东西。 - undefined
atomic_init
在C++20中已被弃用 :( 请参见http://www.wg21.link/p0883 - dypstd::atomic_init
已经过时,因为构造函数保证会执行必要的操作,使对象可用,包括在静态存储中进行默认构造(零初始化)或放置新对象。请参阅https://en.cppreference.com/w/cpp/atomic/atomic_init - 这仅用于与C兼容,C允许您编写类似_Atomic int foo;
而不是_Atomic int foo = 0;
的局部变量。在C中,这是如何使用malloc空间来保存原子对象的:C文档:https://en.cppreference.com/w/c/atomic/atomic_init - undefinedstd::atomic_init
是不被允许的,但是似乎std::array
在没有指定初始化器的情况下会进行默认构造。如果对一个对象使用两次,也是不被允许的,所以不能保证在使用过后再次使用这种方式将数组清零能够正常工作。但是第一次使用的时候,我不知道为什么你会选择循环而不是直接使用array< atomic_size_t, 10 > arr = {}
。 - undefined