用途
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消耗两者的一种取舍。