nginx无锁机制的学习

在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)

在windows上,相应的API是

InterlockedCompareExchange

在C++11中,进行了跨平台的扩展,STL的函数是

atomic_compare_exchange_weak(

在GCC4.1之前的版本,可以用函数来实现CAS操作,在nginx中,给出了实现,也是CAS的原理

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);

用mutex=0表示无线程用count,用mutex=1来表示有人用count。

其中usleep(10000)是我随便写的一个数,来给CPU空闲的事情,在nginx中,用ngx_cpu_pause来给cpu的空闲。

其中ngx_cpu_pause的实现为

 __asm__ ("pause")

这里经测试,CAS操作的性能比mutex提升的超过10倍,而cpu的使用率基本一致,这应该是nginx高效的原因。

在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才是正经的道理。



你可能感兴趣的:(网络,c++)