在nginx中,广泛应用了CAS(compare-and-swap)操作来完成进程同步。
包括ngx_spinlock,ngx_trylock,ngx_rwlock_wlock,ngx_rwlock_rlock,ngx_shmtx_trylock,ngx_shmtx_lock等各种锁,均由ngx_atomic_cmp_set的CAS操作来实现。
CAS操作作为原子操作,在linux上最低的支持版本是GCC4.1,API是
__sync_bool_compare_and_swap(lock, old, set)
InterlockedCompareExchange
atomic_compare_exchange_weak(
static ngx_inline ngx_atomic_uint_t
ngx_atomic_cmp_set(ngx_atomic_t *lock, ngx_atomic_uint_t old,
ngx_atomic_uint_t set)
{
if (*lock == old) {
*lock = set;
return 1;
}
return 0;
}
广泛应用的CAS操作,应该对应于CMPXCHG 汇编指令,是一条原子操作,更确切的实现是这样的:
static ngx_inline ngx_atomic_uint_t
ngx_atomic_cmp_set(ngx_atomic_t *lock, ngx_atomic_uint_t old,
ngx_atomic_uint_t set)
{
u_char res;
__asm__ volatile (
NGX_SMP_LOCK
" cmpxchgq %3, %1; "
" sete %0; "
: "=a" (res) : "m" (*lock), "a" (old), "r" (set) : "cc", "memory");
return res;
}
既然nginx中CAS是可以替代各种锁,那用CAS操作当然可以替代最简单的mutex的互斥锁。
替代的方法如下:
int mutex = 0;
int lock = 0;
int unlock = 1;
while (!(__sync_bool_compare_and_swap (&mutex,lock, 1) ))usleep(10000);//100000
//pthread_mutex_lock(&mutex_lock);
count++;
// pthread_mutex_unlock(&mutex_lock);
__sync_bool_compare_and_swap (&mutex, unlock, 0);
其中usleep(10000)是我随便写的一个数,来给CPU空闲的事情,在nginx中,用ngx_cpu_pause来给cpu的空闲。
其中ngx_cpu_pause的实现为
__asm__ ("pause")
在nginx中,也并非完全不用mutex,仍然有用ngx_thread_mutex_lock的地方,而ngx_thread_mutex_lock的实现是mutex。
用mutex的原因,在于,需要配合条件变量来一起使用,配合ngx_thread_cond_signal,来完成线程和线程之间的任务分发。
使用mutex获取锁分为两阶段,第一阶段在用户态采用spinlock锁总线的方式获取一次锁,如果成功立即返回;否则进入第二阶段,调用系统的futex锁去sleep,当锁可用后被唤醒,继续竞争锁。这样mutex是有进入内核态,线程进行睡眠。
总结一下,mutex加cond的使用场景,是不常进入,有长期等待的环境,长时间保持锁才需要使用,比如任务的开始或结束。
线程频繁切换的地方,可以完全用CAS操作来进行了,当然采用CAS并不是最优的方法,还需要封装一个spinlock才是正经的道理。