其他 自旋锁 (spinlock) 及 nginx 的实现

yfractal · 2018年12月18日 · 328 次阅读

并发一定要处理同步(synchronization)这个问题,而很多同步问题,都要靠锁来解决,所以锁的策略(锁什么、怎么锁)和锁是否高效(用什么锁)会很大程度上影响并发的能力。

自旋锁解决的问题

比较常见的锁是 mutex,如果在尝试获取资源的时候,发现锁已经被占用,线程会 sleep,让其他线程执行,等锁被释放后,才有可能继续执行。

所以,当 mutex 拿不到锁的时候,线程会 sleep,之后被 wakeup,而这些都是相对比较耗时操作。

想要避免这些的话,可以使用自旋锁(spinlock)。

自旋锁(spinlock)的思路是,如果锁被锁住,就让 CPU 空转一段时间,等待锁的释放。以此来避免线程切换。

自旋的实现

在实现上,会使用一个变量表示资源是否被占用,如果这个变量是 0,表示锁是开着的,1 表示被锁了。

使用 compare_and_set 获取并设置这个变量,compare_and_set 有三个参数,变量,预期的值,要设置的值。

如果设置的时候,预期的值和变量的实际值不一样,设置变量会失败,返回 false。compare_and_set 一般会由硬件保证原子性。

如果锁是锁住的,自旋锁就会忙等(busy wait)一段时间,再次尝试获得锁。

自旋锁除了可以更快速获取资源外,还可以保持当前线程一直处在执行状态。

比如 Erlang scheduler 没有任务的时候,会调用 spinlock 忙等,避免 scheduler 的线程被挂起,从而获得更好的并发性能。

自旋锁一般适用于资源很快被释放的情况,因为 CPU 在等待过程中,一直在空转。如果一个资源会被占用很长时间,使用自旋锁明显比较浪费资源,这个时候 mutex 是更好的选择。

如果很多个线程同时竞争一个锁,冲突的可能就会变大,则可以使用指数退避算法来避免这个问题。

nginx 的实现

下面来看一下 nginx 的实现(去掉了一些不相关的代码)。


void
ngx_spinlock(ngx_atomic_t *lock, ngx_atomic_int_t value, ngx_uint_t spin)
{
    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();
    }

}

首先,如果只有一个 CPU core 的话,直接调研 ngx_sched_yield,ngx_sched_yield 会调用 sched_yield 让出 CPU。也就是说单核 CPU 完全不需要自旋锁(这个应该就不需要解释了)。

之后 n 会随指数增长 n <<= 1,每次循环都会调用 ngx_cpu_pause,循环执行完之后,会调用 ngx_atomic_cmp_set 尝试获取锁。

ngx_cpu_pause 的实现是,如果 CPU 支持 pause 指令的话,会调用 pause 指令,如果不支持的话会使用 nop

nginx 里使用情况

ngx_spinlock(&ngx_thread_pool_done_lock, 1, 2048);

*ngx_thread_pool_done.last = task;
ngx_thread_pool_done.last = &task->next;

ngx_memory_barrier();

ngx_unlock(&ngx_thread_pool_done_lock);

其他

关于锁还有很多有趣的问题可以研究,比如 pthread mutex 为了性能,其实是会 user mode spin 一段时间的。所以多数的情况下,使用 mutex 是更合适的。 再比如 mutex 和 spinlock 的性能对比。再比如 queuing lock 可以更进一步优化新能。本文主要简单介绍自旋锁以及其一种实现,所以并不展开过多话题。

链接

暂无回复。
需要 登录 后方可回复, 如果你还没有账号请点击这里 注册