我有一个问题,我需要能够uint64_t
同时以原子方式更新 TWO 。原子地编写它们中的每一个(例如有两个std::atomic<uint64_t>
)很容易,但这仍然会导致一个被更新而另一个没有被更新的情况。使用锁和互斥锁也很容易实现。
但是我想原子地编写两者,没有任何类型的锁,这样我仍然可以拥有类型的成员变量,uint64_t
这样就没有读取锁定。这是因为我的用例涉及阅读它们很多很多次,但编写它们的次数非常少(~读取 1x/ms,写入 1x/5 分钟)。可能吗?如果是这样,怎么做?
我有一个问题,我需要能够uint64_t
同时以原子方式更新 TWO 。原子地编写它们中的每一个(例如有两个std::atomic<uint64_t>
)很容易,但这仍然会导致一个被更新而另一个没有被更新的情况。使用锁和互斥锁也很容易实现。
但是我想原子地编写两者,没有任何类型的锁,这样我仍然可以拥有类型的成员变量,uint64_t
这样就没有读取锁定。这是因为我的用例涉及阅读它们很多很多次,但编写它们的次数非常少(~读取 1x/ms,写入 1x/5 分钟)。可能吗?如果是这样,怎么做?
因为std::atomic
标准说(强调我的)
主
std::atomic
模板可以用任何TriviallyCopyable类型 T实例化:struct Counters { int a; int b; }; // user-defined trivially-copyable type std::atomic<Counters> cnt; // specialization for the user-defined type
uint64_t
所以你可以像这样创建一个 2 的结构
struct atomic128 {
uint64_t a1, a2;
};
这很容易复制(很容易用 确认std::is_trivially_copyable
),然后使用std::atomic<atomic128>
. 如果不能轻易复制,你会得到一个错误std::atomic<type>
这样编译器将自动使用无锁更新机制(如果可用)。无需做任何特别的事情,如有必要,只需使用以下任何一项进行检查
除了 std::atomic_flag 之外的所有原子类型都可以使用互斥锁或其他锁定操作来实现,而不是使用无锁原子 CPU 指令。有时也允许原子类型是无锁的:例如,如果只有某些子架构支持给定类型的无锁原子访问(例如 x86-64 上的 CMPXCHG16B 指令),则原子是否无锁可能不是直到运行时才知道。
这是编译器资源管理器的演示。如您所见lock cmpxchg16b
,发出了 a ,尽管 GCC 7 及更高版本只会调用__atomic_store_16
which 在内部使用cmpxchg16b
(如果可用)
在某些平台long double
上是 128 位类型或填充到 128 位,因此std::atomic<long double>
可能是另一种解决方案,但您当然需要先检查它的大小以及它是否无锁
另一种选择是Boost.Atomic。它也有宏BOOST_ATOMIC_INT128_LOCK_FREE
和BOOST_ATOMIC_LONG_DOUBLE_LOCK_FREE
检查
在某些 CPU 上 128 位 SSE 操作也是原子的,不幸的是没有办法检查你是否可以使用它
我不认为可以直接做,但你可以做的是使用软件事务内存技术来伪造它。特别是,您可以使用 uint64_t-pairs 的无锁环形缓冲区。在这种配置中,非原子地写入环形缓冲区的非活动元素是安全的,因为在“当前值索引”以原子方式更新之前,没有人会从环形缓冲区的该元素中读取写入结束(这是可能的,因为索引可以是 int32_t)。
警告:这个技巧只有在你能保证在短时间内不会有太多写入值的情况下才有效(其中“太多”意味着“超过环形缓冲区中的插槽数)。此外,我建议找到一个 STM 库来实现这一点,而不是自己动手,因为众所周知,无锁编程很难 100% 正确。