« 预运算地图寻路的一种方法 | 返回首页 | 关于 skynet 调度器的一点想法(续) »

把 skynet 的原子操作换成了 stdatomic

stdatomic 已经是 C11 的标准,并且成为了 C++ 标准的一部分。msvc 也将会支持 stdatomic 。在 skynet 项目开始的时候,还没有这个可以用,所以我采用的是更早一点的 gcc sync 系列的扩展

我想,用 stdatomic 来实现原子操作,会有利于 skynet 未来的发展。上周花了点时间熟悉这套 api 并在 skynet 中实现。同时保留了之前的实现,如果编译器定义了 __STD_NO_ATOMICS__ 就会切换回老版本。

首先,C11 增加了 _Atomic(T) 类型。这在之前的 gcc Atomic builtins 里没有。过去是直接使用已有的原生数据类型的。现在,如果一个 int 是一个原子变量,就需要使用 atomic_int ,它实际上是 _Atomic(int)

原子操作的 api 只接受原子变量,即使是简单的读写原子变量,也必须用 atomic_loadatomic_store 。之前则没有提供相关 api 。这样代码更为严谨,你不必假设系统到底能原子读写多长的字,编译器会做检查。

不过,gcc 似乎并没有严格检查原子操作传入的变量是否是原子类型;而 clang 则严格的多。所以一开始我实现的初版在 clang 上遇到的编译操作,就是因为改漏了一些地方,经过网友提醒,后来才修补完整。


cas 指令 ,即先比较旧值,只有在相等的情况下才做交换。这是无锁结构和一些基本锁实现的基础。例如 CAS(ref, 0, 1) 表示比较 *ref 是否为 0 ,如果不等于 0 就返回失败;等于 0 就返回成功并把 *ref 置为 1 。

这可以在并发操作 *ref 的时候,同时只有一处可以把 *ref 从 0 修改为 1 。

在新标准中, cas 分成了两个版本, atomic_compare_exchange_weakatomic_compare_exchange_strong 。weak 版本允许偶发情况下,即使相等也失败(对于上面的例子来说,允许当 *ref == 0 的时候失败)。我们一般使用这个 weak 版本即可(相对 strong 版本成本可能更低)。

不过让我奇怪的是,新标准的 cas api 的 oval 旧值是用指针传递,而新值用的值传递。这个旧值指针是一个传统指针,不是原子类型。我不太理解这个设计的原因。之前的 atomic builtins 和 windows 的 InterlockedCompareExchange 都是传值的。

之前的 __sync_lock_test_and_set 变成了 atomic_flag_test_and_set ,必须使用 atomic_flag 。这个东西可以用来实现 spinlock 。但让我奇怪的是,C11 标准中没有 atomic_flag_test (在 C++20 中有)。而实现读写锁则需要 test (但不 set),不过没太大关系,我们可以用 cas 指令代替。

在以前的 atomic builtins 中,既有 __sync_fetch_and_add 又有 __sync_add_and_fetch ,区别在于返回值是加之前的还是加之后的。stdatomic 中只保留了 atomic_fetch_and_add ,取消了后者。不过没太大关系,因为,以自增 1 为例,add_and_fectch(ref,1) 等价于 fetch_and_add(ref, 1)+1

最后一个小问题是,指针没有定义 atomic 类型,所以我用 atomic_uintptr_t 替代。但是在用的时候,需要再转换为对应的指针类型。

Comments

很早就收藏过你的博客,直到现在看了一本C语言的书有你的序,突然想起来。

只是来看看

CAS/atomic exchange指令一般都是在寄存器和内存之间操作,只有对内存的那一次操作才是原子的,原值返回在寄存器里,再写入变量需要额外的store,这个过程不是伴随CAS一起的原子操作,因而无法使用原子变量,只能用普通指针或者返回值。

举个例子,写入无锁链表的操作是:

Node* t = .....;
while (!head.compare_exchange_weak(t->next, t, std::memory_order_release));

而不能使用:
t->next = head.exchange(t, std::memory_order_release); // 写入t->next和exchange是两次写存,不在同一原子事务中

同理,std::swap也不能原子性地交换两个atomic的变量。

这个 weak 应该是为了方便 SC/LL 实现用的,性能在支持 SC/LL 的硬件上应该会更好

看到云风大哥还是一如既往地为skynet输出,我就放心啦

Post a comment

非这个主题相关的留言请到:留言本