Nginx中的原子操作

文章出自云中楼阁,欢迎转载交流


原子操作在系统编程中属于基础工具。nginx与其他软件很不一样,它几乎把所有操作系统的功能都封装了一遍,估计是出于编程一致性和可移植性的考虑。对于原子操作,nginx提供一组接口。

这里我们只研究有GCC 4.1以上版本的情况,因为nginx对不同的情况有不同的实现,GCC应该还是比较普遍的吧

先看些基本类型

typedef long ngx_atomic_int_t;
typedef unsigned
long ngx_atomic_uint_t;
typedef volatile ngx_atomic_uint_t ngx_atomic_t;
#if (NGX_PTR_SIZE == 8)
#define NGX_ATOMIC_T_LEN (sizeof("-9223372036854775808") - 1)
#else
#define NGX_ATOMIC_T_LEN (sizeof("-2147483648") - 1)
#endif

GCC中的long类型的长度始终是操作系统的位数,所以这一段代码是可以兼容64位系统的。 另外注意原子类型ngx_atomic_t一定要加上volatile标志,如果不加的话,原子变量可能被编译器优化到寄存器去了,就不可能利用原子操作去实现锁的功能了。  

再看nginx提供了哪些原子操作

#define ngx_atomic_cmp_set(lock, old, set) __sync_bool_compare_and_swap(lock, old, set)
#define ngx_atomic_fetch_add(value, add) __sync_fetch_and_add(value, add)
#define ngx_memory_barrier() __sync_synchronize()
#define ngx_cpu_pause() __asm__("pause")

第一个接口是“如果lock指向的值等于old,那么把lock指向的内存单元设为set,并返回1”,其他情况则返回0;
第二个接口是“将value指向的值增加add,返回增加以前的值”;
第三个接口的功能也有点儿诡异,假如你在代码中放上这个函数,那么编译器就不会为了优化而打乱这个函数前后的指令,这也是为什么它取名叫barrier,相当于一个屏障,阻止编译器把后面的指令搬到barrier前面去;
第四个接口是一条汇编指令,暂停程序运行。

其实后面两个接口跟底层原子操作关系不是非常紧密,可能是作者觉得放在这里最合适吧。


前面说过原子操作是基础设施,我们有很多其他的功能要依赖它,比如实现锁

#define ngx_trylock(lock) (*lock==0 && ngx_atomic_cmp_set(lock, 0, 1))
#define ngx_unlock(lock) *(lock) = 0

ngx_trylock顾名思义是尝试着去加锁,能加则加,不能则向调用者报告失败,1代表成功,0代表失败。不过我对这个实现有点儿疑问,直接使用ngx_atomic_cmp_set就可以了啊,为什么还要在前面加一个玩意儿,难道是为了性能?还有一点使用者要注意,lock一定要是ngx_atomic_t类型的,这是个宏,是不会做类型检查的。
ngx_unlock毋庸赘言了吧,就是解锁的。

nginx还为我们实现了自旋锁,它的声明同样放在os/unix/ngx_atomic.h中,实现确是core/ngx_spinlock.c

void ngx_spinlock(ngx_atomic_t *lock, ngx_atomic_int_t value, ngx_uint_t spin)
{
#if (NGX_HAVE_ATOMIC_OPS)
ngx_uint_t i, n;
for ( ;; ) {
if (*lock == 0 && ngx_atomic_cmp_set(lock, 0, value)) {
return;
}

if (ngx_ncpu > 1) {
for (n = 1; n < spin; n <<= 1) {
for (i = 0; i < n; i++) {
ngx_cpu_pause();
}
if (*lock == 0 && ngx_atomic_cmp_set(lock, 0, value)) {
return;
}
}
}哦u
ngx_sched_yield();
}
#else
#if (NGX_THREADS)
#error ngx_spinlock() or ngx_atomic_cmp_set() are not defined !
#endif
#endif
}

我以前理解的自旋锁是进程会一直死循环,直到可以申请锁为止。这样就必须要求处理器是多核的,否则就你一个进程在CPU上死循环了。从这个实现可以看出,当CPU是单核时,自旋锁并不无休止地试探,而是申请不到锁时直接挂起让其他进程运行。并且即使CPU是多核的,也没有死循环,这里就有点儿看不懂了,如果哪位网友清楚的话请告诉我。


最后总结下ngx_atomic.h为我们提供了哪些功能吧
ngx_atomic_int_t,    ngx_atomic_uint_t,    ngx_atomic_t,   NGX_ATOMIC_T_LEN 四种类型

int ngx_atomic_cmp_set(ngx_atomic_t *lock, ngx_atomic_uint_t old, ngx_atomic_uint_t set);

ngx_atomic_uint_t ngx_atomic_fetch_add(ngx_atomic_t *value, ngx_atomic_int_t add);

void ngx_spinlock(ngx_atomic_t *lock, ngx_atomic_int_t value, ngx_uint_t spin);  

void ngx_memory_barrier();

void ngx_cpu_pause();

 

这篇文章分析了nginx中os/unix/ngx_atomic.h和core/ngx_spinlock.c,希望能和网友多多交流。 

posted on 2010-12-24 21:48  Joevsky  阅读(1812)  评论(1编辑  收藏  举报

导航