自旋锁分析

背景

了解下自旋锁的知识点

自旋锁

我的印象中主要是为了防止资源竞争时,避免线程切换上下文浪费时间片,而使用的一种锁,它的目的就是保证短时间内能够竞争到资源,避免时间片浪费而提升性能。

但我一直有个疑问,不释放时间片,难道一直死循环吗?这也太不优雅了吧?

看了下ngx的源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
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;
}
}
}

ngx_sched_yield();
}

#else

#if (NGX_THREADS)

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

#endif

#endif

}

其中涉及到原子操作、cpu_pause、和sched_yield 方法。

原子操作

主要是指cas的原子操作 CAS操作——Compare & Set,或是 Compare & Swap,现在几乎所有的CPU指令都支持CAS的原子操作。 没有这个原子指令,很多功能都完成不了。

  • GCC的CAS
1
2
3
4
GCC4.1+版本中支持CAS的原子操作(完整的原子操作可参看 GCC Atomic Builtins)

bool __sync_bool_compare_and_swap (type *ptr, type oldval type newval, ...)
type __sync_val_compare_and_swap (type *ptr, type oldval type newval, ...)
  • Windows的CAS
1
2
3
4
5
在Windows下,你可以使用下面的Windows API来完成CAS:(完整的Windows原子操作可参看MSDN的InterLocked Functions)

InterlockedCompareExchange ( __inout LONG volatile *Target,
__in LONG Exchange,
__in LONG Comperand);
  • C++11中的CAS
1
2
3
4
5
6
7
8
C++11中的STL中的atomic类的函数可以让你跨平台。(完整的C++11的原子操作可参看 Atomic Operation Library)

template< class T >
bool atomic_compare_exchange_weak( std::atomic* obj,
T* expected, T desired );
template< class T >
bool atomic_compare_exchange_weak( volatile std::atomic* obj,
T* expected, T desired );

cpu_pause

具体说明看链接

主要是两个功能

  • 在循环过程中,避免内存顺序违规导致性能下降

  • 循环等待时,降低cpu功耗

    boost 跨平台实现

1
2
3
4
5
6
7
8
BOOST_FORCEINLINE void pause() BOOST_NOEXCEPT
{
#if defined(_MSC_VER) && (defined(_M_AMD64) || defined(_M_IX86))
_mm_pause();// #include <emmintrin.h>
#elif defined(__GNUC__) && (defined(__i386__) || defined(__x86_64__))
__asm__ __volatile__("pause;");
#endif
}

sched_yield

暂时的让出时间片 ,一般都是 usleep/Sleep(0) 或 usleep/Sleep(1) 或 cpu_pause 。

我们自己需要实现自旋锁吗

不需要。2009年linux内核已经把 mutex修改成了自适应锁:

  • 一阶段使用自旋锁,在忙等待一段时间后,若还不能获得锁,则转变成普通锁

  • 二阶段普通锁:阻塞线程

使用c++11 std::mutex 也有相同的性能。 实际测试中, std::mutex 比windows的 CRITICAL_SECTION 还是有10%左右的性能提升。