如果要在多线程中对同一个整型变量进行加减操作,我们知道可以通过加锁的方式保证线程同步,但仅对这一个变量加锁,是不是大材小用了?有没有类似于Linux内核中的atomic_inc()/atomic_dec()方法从指令的层面上实现操作变量的原子性?
答案是,有的。GCC提供了一系列内置函数,来完成对一些简单的数据操作的同步。
type __sync_fetch_and_add (type *ptr, type value, ...)
type __sync_fetch_and_sub (type *ptr, type value, ...)
type __sync_fetch_and_or (type *ptr, type value, ...)
type __sync_fetch_and_and (type *ptr, type value, ...)
type __sync_fetch_and_xor (type *ptr, type value, ...)
type __sync_fetch_and_nand (type *ptr, type value, ...)
这些builtin函数的作用通过名字就可以看出来,相当于:
{ tmp = *ptr; *ptr op= value; return tmp; } //'op'可以是add,sub,or, etc.
{ tmp = *ptr; *ptr = ~tmp & value; return tmp; } // nand
这些函数的返回值为内存中原来的值。
注1:其中类型type可以是1/2/4/8字节长度的整数类型或指针类型,下同。
注2:函数原型中的省略号可指明需要memory barrier的变量的列表,目前GCC忽略这个列表,保护所有全局可见的变量,下同。
type __sync_add_and_fetch (type *ptr, type value, ...)
type __sync_sub_and_fetch (type *ptr, type value, ...)
type __sync_or_and_fetch (type *ptr, type value, ...)
type __sync_and_and_fetch (type *ptr, type value, ...)
type __sync_xor_and_fetch (type *ptr, type value, ...)
type __sync_nand_and_fetch (type *ptr, type value, ...)
这些builtin函数相当于:
{ *ptr op= value; return *ptr; }
{ *ptr = ~*ptr & value; return *ptr; } // nand
这些函数的返回值为内存中新的值。
这样,我们就可以自定义一个原子的自增操作:
#define atomic_inc(x) __sync_add_and_fetch(&(x), 1)
bool __sync_bool_compare_and_swap (type *ptr, type oldval type newval, ...)
type __sync_val_compare_and_swap (type *ptr, type oldval type newval, ...)
这两个函数意思是,如果*ptr的值等于oldval(即ptr指向的内存中的值是旧值),则将新的值newval写到*ptr中。
其中第一个函数的返回值为bool类型,意思是,如果*ptr==oldval且newval写入成功,则返回true。
第二个函数则返回操作之前的*ptr的值。
__sync_synchronize (...)
这个函数会制造一个full barrier。
type __sync_lock_test_and_set (type *ptr, type value, ...)
将*ptr的值设为value,返回*ptr中操作之前的值。
void __sync_lock_release (type *ptr, ...)
释放*ptr,即 *ptr = 0 。
我们可以用这些函数对多线程中简单的数据操作做同步,而无需加锁,节省了任务调度的开销。如果在目标处理器上没有实现这些(或其中某些)内置函数,则会报出warning。
实际上,GCC实现这些内置函数是为了与 Intel Itanium Processor-specific Application Binary Interface 的7.4节的内容做兼容,也因此这些函数的名字没有以”_builtin“开头。这份文档要求这些内置函数除了实现特定的同步效果外,还需要具备如下两个重要属性:
1)每个函数的操作都是原子性的(例如在MIPS中通过LL/SC指令实现)。
2)每个函数都会实现某种内存屏障,上述__sync_lock_test_and_set为”acquire barrier”,__sync_lock_release为”release barrier”,其他均为”full barrier”(三种内存屏障的解释在文档中有说明,对应了内核中的rmb()/wmb()/mb())。
参考资料
[1]: http://gcc.gnu.org/onlinedocs/gcc-4.1.1/gcc/Atomic-Builtins.html GCC atomic-builtins
[2]: https://uclibc.org/docs/psABI-ia64.pdf Intel processor-specific ABI