我遇到一个问题,我需要能够同时原子地更新两个uint64_t
。很容易以原子方式编写每一个(例如有两个std::atomic<uint64_t>
),但这仍然会导致一个更新但另一个不更新的情况。使用锁和互斥锁也很容易实现。
但是我想原子地编写,没有任何锁定,所以我仍然可以拥有类型为uint64_t
的成员变量,这样就没有锁定读取。这是因为我的用例涉及阅读它们很多很多次,但写得非常少(〜读1x / ms,写1x / 5分钟)。可能吗?如果是这样,怎么样?
答案 0 :(得分:3)
对于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
所以你可以像这样创建一个2 uint64_t
的结构
struct atomic128 {
uint64_t a1, a2;
};
可以轻松复制(使用std::is_trivially_copyable
很容易确认),然后使用std::atomic<atomic128>
。您将收到错误if std::atomic<type>
is not trivially copyable
这样编译器会自动使用无锁更新机制(如果可用)。无需做任何特别的事情,只需在必要时检查以下任何一项
编译器资源管理器上的除了std :: atomic_flag之外的所有原子类型都可以使用互斥锁或其他锁定操作来实现,而不是使用无锁原子CPU指令。原子类型也允许有时无锁:例如,如果只有一些子体系结构支持给定类型的无锁原子访问(例如x86-64上的CMPXCHG16B指令),则原子是否是无锁的可能不是直到运行时才知道。
Here's a demo。如您所见,lock cmpxchg16b
已发出,但GCC 7及以上只会调用__atomic_store_16
long double
std::atomic<long double>
在某些平台上{{1}}是128位类型或填充到128位,因此{{1}}可能是另一种解决方案,但当然您需要检查其大小以及它是否无锁或不首先
另一种选择是internally use cmpxchg16b
if it's available。它还有要检查的宏Boost.Atomic
BOOST_ATOMIC_INT128_LOCK_FREE
and BOOST_ATOMIC_LONG_DOUBLE_LOCK_FREE
在某些CPU上,128位SSE操作也是原子操作,遗憾的是,无法确定是否可以使用它
答案 1 :(得分:1)
我认为不可能直接做,但你可以做的是使用software transactional memory techniques来伪造它。特别是,您可以使用uint64_t-pairs的无锁环缓冲区。在这种配置中,写入环形缓冲区的非活动元素是非安全的非原子化,因为没有人会从环形缓冲区的那个元素读取,直到“current-value-index”在原子上更新为止写入结束(这是可能的,因为索引可以是int32_t)。
警告:这个技巧只有在你可以保证在短时间内没有太多的值写入时才有效(其中“太多”意味着'超过环形缓冲区中的插槽数) )。另外我建议找一个实现这个的STM库,而不是自己编写,因为无锁编程很难100%正确。