自动更新结构的两个成员的最佳方法?

时间:2014-12-02 11:12:10

标签: c++ c++11 atomic

我有一个包含两个数据成员的结构:

struct A{
    short b;
    short c;
};

和包含许多这些结构对象的向量:

std::vector<A> vec;

在运行时,struct对象中的数据成员A.bA.c设置为零/值x。但是,bc必须同时修改 - 如果没有另一个,则无法单独更新。我打算使用原子compare_exchange_weak()来进行更新。

我不确定是否应该在向量中将每个结构表示为std::atomic<A>,或者我是否应该在结构中放置一个联合,以将两个短路组合成单个uint32_t和然后修改:

union A {
    struct {
         short b;
         short c;
    };

    uint32_t d;
};

什么是最好的解决方案?我应该存储以下的矢量:

std::vector<uint32_t>

并在访问每个元素时,将reinterpret_cast重新解释为A,以获取d

我希望锁定尽量少打扰。

不需要可移植性,这将在Linux,64位,x86,GCC 4.8+编译器上

4 个答案:

答案 0 :(得分:4)

除非你所针对的硬件支持双重比较和交换(可能不是这种情况),我认为你只有两个可移植的解决方案:

  1. 根据您的偏好引入更高级别的锁定(互斥锁或自旋锁),并在获取的锁定范围内的bc上执行所有操作。互斥锁很重,但即使在高争用情况下,std::atomic_flag也是无锁且非常轻的。

  2. 将两个成员合并为一个std::atomic<int>,并通过位屏蔽将int拆分为short。请注意,这需要sizeof(int) >= 2 * sizeof(short)。如果需要强制执行,请使用固定大小的整数类型。

  3. 当然,要确定哪种解决方案是最快的基准测试。

    如果您知道编译时需要struct A的数量,我建议将它们放入std::array。如果不这样做,只要此数字在向量的整个生命周期内保持不变,std::vector就可以了。否则,由于std::atomic<T>既不可复制也不可移动,因此 必须为struct A编写自己的复制/移动构造函数。

答案 1 :(得分:2)

我建议将变量包装在一个带有由互斥锁保护的getter和setter的类中,并使变量变为私有。

使用联合可能会导致基于机器体系结构和编译器标志的无法预料的功能。

编辑运行存储给定结构类型值的简单程序的结果(Linux 32位,x86):

  • 简单商店(完全没有保护) - &gt; ~4000 us
  • Mutex守卫商店 - &gt; ~12000 us
  • 使用具有原子聚合字段的联合 - &gt; ~21000 us

答案 2 :(得分:1)

只需制作一个足够大的原子类型的union。这就是我使用的(代码片段不是完全可移植,使用<cstdint>类型而不是shortint肯定会更喜欢 - 但它&# 39;对我来说足够好,因为它实际上永远是完美无缺的:

union A {
    struct {
         short b;
         short c;
    };

    std::atomic<int> d;
};

(事实上,我的实现稍微复杂一点:我将整个事情包装成另一个struct的习惯,因此A是一个包含struct的{​​{1}} {1}}而不是union。传统上union有关于构造函数的奇怪约束,我的初始实现早于C ++ 0x,而我的union需要构造函数。但当然使用C ++ 11&#39; A这些考虑因素已经过时,因为那些人​​为约束已经不复存在了)

请注意,<atomic> 可能无锁,但不能保证(std::atomic除外)。在实践中,对于任何大小为boolint的内容,它在每个严重的,不开玩笑的情况下都是无锁的。在大多数现代架构中,它也可以锁定指针大小(尽管存在例外情况,特别是AMD的第一代x86_64芯片)。

答案 3 :(得分:0)

由于可移植性不是问题,因此锁定只会像您希望的那样具有侵入性(将“侵入性”表示为CAS循环或过度锁定)。

在您的情况下,您可以直接使用atomic builtins

AFAIK,您应该能够混合使用字词大小直接在bc以及a上操作。我从来没有这样做过,所以我不能说它是否能在所有x86事件上可靠地运行。一如既往:原型和测试,测试,测试!