如何将std::atomics数组初始化为零?

10
array< atomic_size_t, 10 > A;

无论是atomic_init(A,{0})还是A = {ATOMIC_VAR_INIT(0)}都似乎不起作用,会返回一个难以理解的错误。您如何将原子数组初始化为0?

即使使用循环逐步更新数组中的一个元素也不起作用。如果我们无法初始化数组,那么原子数组的目的是什么?

我还想补充一点,我的实际数组大小非常大(不像示例中的10),因此我需要直接初始化。

4个回答

6
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}};
};

很遗憾,atomic_init在C++20中已被弃用 :( 请参见http://www.wg21.link/p0883 - dyp
@dyp 替换为什么? - undefined
1
@Zebrafish:std::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 - undefined
1
请注意,如果对一个未进行默认构造的对象调用std::atomic_init是不被允许的,但是似乎std::array在没有指定初始化器的情况下会进行默认构造。如果对一个对象使用两次,也是不被允许的,所以不能保证在使用过后再次使用这种方式将数组清零能够正常工作。但是第一次使用的时候,我不知道为什么你会选择循环而不是直接使用array< atomic_size_t, 10 > arr = {} - undefined

4
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 是否也适用)。

一段时间前曾发布过类似的问题:线程安全的无锁数组


好的,但我把10作为示例。实际上,我应该使用大量零初始化我的数组。 - Pippo
那么就不可能有一个原子变量的集合?!o.O - Pippo
有点类似。你必须跳过几个小障碍。其他的SO问题中有一个如何做到这一点的例子。 - Zac Howland
事实是,在发帖之前我注意到了你链接的那个问题,但天真地以为他们想要做更高级的东西。因为我“只是”想初始化原子静态数组,所以我认为应该有更简单的方法。此外,因为网络上有人像我一样定义原子数组;现在我无法想象他们如何使用它们。 - Pippo
抱歉,我不得不投反对票,这个答案是错误的。请看下面我的答案。 - Walter
显示剩余2条评论

3

实际初始化,当数组正在构建时

对于实际初始化,而不是清零现有数组,一个空的初始化列表就可以正常工作,就像对于普通的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或其他方式)。
请参阅高效重置包含std::atomic成员的结构体数组的方法?以获取更多详细信息,包括在实际机器上的汇编中,使用对齐的宽存储(例如16字节或32字节)来将整个数组清零,可以原子地将每个元素清零,因此如果您要循环遍历数组并将每个元素赋值为零,您确实希望利用这一点。由于编译器不会对原子操作进行优化,因此每个元素都会产生一个单独的8字节存储。(这并不是灾难,但对于像atomic<uint8_t>这样的较小类型来说,确实更差。)请参阅向量加载/存储和聚集/散射的每个元素的原子性? - x86手册不能保证这一点,尽管在实际硬件上几乎肯定是正确的。

顺便说一下,这在Godbolt上可以使用clang3.5和GCC4.7(最早支持-std=c++11)来运行,所以它不依赖于任何新的编译器特性。 - undefined

1
你可以像下面展示的那样使用std::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};
}

工作演示


有没有办法在C++11中以递增索引的方式初始化这个数组,就像{0,1,2,3,4,5,6,7}一样?我知道我可以使用你的解决方案,并将Indices-Indices替换为Indices,但这需要C++20。 - undefined
是的,很可能在C++11中有一种方法可以实现你所要求的。请随时提出一个新的独立问题来回答你的后续问题。 - undefined
这似乎对于零初始化来说过于复杂了。我是否错过了一些原因,这样做比我后来的答案更可取,还是只是没有意识到零是特殊的,一个空的初始化列表就可以解决问题了? - undefined
@user12002570: ... arr {}适用于任何原始类型(包括FP和指针),也适用于原子聚合物(https://godbolt.org/z/s7dTYcW73)。 atomic_size_t对于除了std::index_sequence vs. std::integer_sequence<T>之外的任何东西都不是特别特殊。这个答案有用的地方在于它被泛化为非零init时,可以使用线性序列或者可能是左移单个位的递归,或者一些数学函数,或者其他任何东西。 - undefined
1
@PeterCordes 我明白了,所以这个通用版本的主要用途是用于非零初始值。 - undefined
显示剩余4条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接