nginx 自旋锁学习

用途

spin_lock的使用场景是你预知很快能获得资源,快速把cpu用起来。

nginx源码:

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/*1*/)) {
            return;
        }

        if (ngx_ncpu > 1) {

            for (n = 1; n < spin /*1024*/; n <<= 1) {

                for (i = 0; i < n; i++) {
				
                    ngx_cpu_pause();
                }

                if (*lock == 0 && ngx_atomic_cmp_set(lock, 0, value)) {
                    return;
                }
            }
        }

        ngx_sched_yield();
    }

#else

#if (NGX_THREADS)

#error ngx_spinlock() or ngx_atomic_cmp_set() are not defined !

#endif

#endif

}

1、ngx_atomic_cmp_set 函数意义

NGX_CASA()
这是一个cas操作,是 Compare And Swap 的缩写。
它的意义是这样的,CAS操作包含三个操作数,内存位置(V)、预期原值(A)和新值(B)。如果内存位置的值与预期原值相等,那么处理器会自动将该位置值更新为新值。否则,处理器不做任何操作。无论哪种情况,它都会在CAS指令之前返回该位置的值。CAS有效地说明了“我认为位置V应该包含值A;如果包含该值,则将B放到这个位置;否则,不要更改该位置,只告诉我这个位置现在的值即可”。

在 x86 平台上,有一条指令:cmpxchgl。这条指令就是一条原子指令,在执行这条指令的中间,线程是不会被切换的。也就是说,我们完成上面所说的复杂语义,却不用自己加锁来进行特别的处理。

伪代码如下:

 void compareAndSwap(UserObject o, Value a, Value b) {
    Value old = o.value;
    if (o.value == a)
        o.value = b;
    return old;  //返回原始的值
}

毫无疑问,上面的代码在执行效率上比起一条CPU指令差了非常非常多。
上述cas说明来自:https://zhuanlan.zhihu.com/p/26593887

ngx_atomic_cmp_set (lock, 0, value)
{
set = NGX_CASA(set, old, lock);
/记录lock的值,当lock == old,也就是lock==0, *lock=set(1),ngx_casa返回0,lock原始值,cmp_set
返回1,否则返回0
/
return (set == old);
}

解锁
#define ngx_unlock(lock) *(lock) = 0
当另一个线程解锁,当前线程ngx_atomic_cmp_set 退出,拿到锁。

2、 ngx_cpu_pause()函数
ngx_cpu_pause() 函数是 x86指令pause,作用如下:

翻译自Intel指令)PAUSE指令提升了自旋等待循环(spin-wait loop)的性能。当执行一个循环等待时,Intel P4或Intel Xeon处理器会因为检测到一个可能的内存顺序违规(memory order violation)而在退出循环时使性能大幅下降。PAUSE指令给处理器提了个醒:这段代码序列是个循环等待。处理器利用这个提示可以避免在大多数情况下的内存顺序违规,这将大幅提升性能。因为这个原因,所以推荐在循环等待中使用PAUSE指令。出处:PAUSE指令

简单点说,用pause可以提示CPU接下来的指令序列是用来自旋等待,就不用做memory reorder了,cache什么的也不用浪费掉了——要知道,cache是很宝贵的资源啊。这指令还有附送的功能——减少能耗。
具体memory reorder的内容,不太了解

在循环中加入cpu_pause指令,可以减少ngx_atomic_cmp_set 的执行,在及时判断lock以及cpu开销两者中,做了一个平衡。

3、 ngx_sched_yield()

#define ngx_sched_yield() sched_yield()
#define ngx_sched_yield() SwitchToThread()
#define ngx_sched_yield() usleep(1)

sched_yield()会让出当前线程的CPU占有权,然后把线程放到静态优先队列的尾端,然后一个新的线程会占用CPU。那这个和sleep()有啥区别呢?
sched_yield()这个函数可以使用另一个级别等于或高于当前线程的线程先运行。如果没有符合条件的线程,那么这个函数将会立刻返回然后继续执行当前线程的程序。
而sleep则是等待一定时间后等待CPU的调度,然后去获得CPU资源(这也是usleep()为什么不准的原因)

来自:blog.csdn.net/zk3326312/article/details/79624199的说明
https://www.cnblogs.com/arnoldlu/p/11287972.html 也有usleep和sched_yield的详细对比

整体流程:

首先执行cas,如果正确,直接退出,接着判断cpu核数,单核使用ngx_sched_yield,让出cpu,等待下次判断(https://www.zhihu.com/question/55764216中 有人提出d_atomic_bool.load( std::memory_order_relaxed )使用这个单核下会进一步优化,都是大牛)。
在多核中,使用ngx_cpu_pause 多次执行cas判断,ngx_cpu_pause和std::memory_order_relaxed 有类似所用,多次无果,使用ngx_sched_yield,让出cpu,轮询判断。

ngx_cpu_pause 是一个亮点,是均衡及时判断和cpu消耗两者的一种取舍。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值