linux锁机制:ticket spinlock

spinlock 

      spinlock即自旋锁,在linux内核中广泛运用的底层同步机制,相对于可睡眠锁,spinlock采用spin的方式获取锁(busy-wait),
避免了context_switch的开销,在短暂临界区访问场景下速度明显提升,性能更高,对memory等critical section互斥访问,发挥着重要作用。
spinlock对内核的数据安全性和并发性能有很大的影响。这些年来,内核开发者在自旋锁的实现上做了大量优化工作。

      早期内核下,如linux2.6.24,自旋锁spinlock用一个整数值来表示,表明了锁是否可用,初值设为1。
spin_lock()函数通过递减val(原子方式),然后查看是否为0,若为0则成功拿锁,若为负数则代表锁已属于他人,所以它进入spin状态,不断查询val值直到变为1。当锁的拥有者完成critical section的执行,将val置为1,即释放锁。

显而易见,这种方式拿锁非常快,尤其是当没有锁竞争的时候,性能非常不错。不过这种方法有一个缺点:它是不公平的。

何为不公平?

当锁的onwer释放锁后,锁的等待者需要发起竞争,这种机制没有办法保证等待时间最长的CPU能优先获得锁,并且激烈的竞争增加了额外的总线开销。

事实上,刚刚释放锁的那个处理器,由于拥有高速缓存原因,很大概率会优先拿到锁,同样无法保证锁的公平性,所以某些场景下spinlock会带来性能损失、实时性降低。
所以在该机制下,我们很难确保一个CPU从申请拿锁到真实获取锁的延迟时间,极端情况下,拿锁的时间可能是任意长。某些高要求实时性的业务场景是不能容忍的。

举个生活中的例子:
spinlock好比火车上上厕所,很多人同时竞争一个厕所,而恰巧你吃了不干净的东西,很捉急,若没有公平性,后果是灾难性的。

ticket机制

来到主题,为解决上述问题,内核引入ticket spinlock,解决了不公平问题,它是如何做到的?

再举个例子:去过银行都知道,办业务先取张票,票上面有一个编号,每新来一个客户编号会加1,银行显示屏上会显示当前正服务的客户编号。
当有多位客户等待时,银行按照编号顺序来对其进行服务。由此,实现了公平性,这类似于FIFO算法。

ticket spinlock就是采用了这种机制,spinlock的val变量被分割成2部分:
next是发票机最后发出的编号,而owner是正在被服务的编号。

ticket-spinlock.png

typedef struct {
        union {
                atomic_t  val;
                struct __raw_tickets {
                        u16 next;
                        u16 owner;
                } tickets;
        };
} arch_spinlock_t;

ticket spinlock伪代码实现

//加锁
spin_lock(lock *l)
{
    int n = atomic_add(1, l.next);  //先拿票
    
    while(n != atomic_read(l.owner) + 1)  //查看是否轮到自己
        cpu_relax();
}
 
//放锁
spin_unlock(lock *l)
{
    atomic_add(1, l.owner); //服务完成,叫下一号
}


假设有8个线程同时竞争锁,如下示例:now_serving代表正在持锁的线程,next_ticket代表最后到来的线程,各线程会按顺序进行拿锁。

    now_serving   next_ticket
        |                    |
        V                  V
... 0 1 2 3 4 5 6 7 8 9 10 ...
         \_________/
           8 threads
           holding one ticket each

linux arm32中spinlock实现:

/*
 * ARMv6 ticket-based spin-locking.
 *
 * A memory barrier is required after we get a lock, and before we
 * release it, because V6 CPUs are assumed to have weakly ordered
 * memory.
 */

static inline void arch_spin_lock(arch_spinlock_t *lock)
{
        unsigned long tmp;
        u32 newval;
        arch_spinlock_t lockval;

        prefetchw(&lock->slock);
        __asm__ __volatile__(
"1:     ldrex   %0, [%3]\n"     /* 原子方式,读取锁的值赋值给lockval */
"       add     %1, %0, %4\n"   /* 将next字段++之后的值存在newval中 */
"       strex   %2, %1, [%3]\n" /* 原子方式,将新的值存在lock中,写入否成功结果存入在tmp中 */
"       teq     %2, #0\n"       /* 判断是否写入成功,不成功跳到标号1重新执行 */
"       bne     1b"
        : "=&r" (lockval), "=&r" (newval), "=&r" (tmp)
        : "r" (&lock->slock), "I" (1 << TICKET_SHIFT)
        : "cc");

        /* 查询是否可以拿锁,若next != owner说明已有人持锁,自旋 */
        while (lockval.tickets.next != lockval.tickets.owner) {
                wfe();
                lockval.tickets.owner = READ_ONCE(lock->tickets.owner);
        }

        smp_mb();
}

/* 释放锁比较简单,将owner++即可 */
static inline void arch_spin_unlock(arch_spinlock_t *lock)
{
        smp_mb();
        lock->tickets.owner++;
        dsb_sev();
}


总结:

ticket spinlock自旋锁出现之前,所有处理器争夺一个锁先看谁能抢到它。现在他们排队等好,获取锁的顺序到达。

虽然ticket spinlock看起来很cool,但已然是过去时了,在较高版本内核版本中spinlock再次变身,升级为queued spinlock,这将带来更大的性能提升。
 

  • 2
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Linux中的spinlock是一种自旋锁机制,用于保护对共享资源的访问,以防止同时访问导致的数据竞争问题。spinlock使用了一种称为自旋的技术,即当一个线程需要获取锁时,它会一直等待,直到锁被释放。这种等待是循环的,即线程会不断地检查锁的状态,直到锁被释放为止。 spinlock相比于传统的互斥量(mutex)和信号量(semaphore)等锁机制,具有更高的性能和灵活性。spinlock不需要使用内核调度器,因此不会产生额外的上下文切换开销。此外,spinlock可以用于任何需要保护的临界区代码,而不仅仅是用于进程之间的同步。 使用spinlock时,需要将其初始化为0,以便其他线程可以安全地访问共享资源。当一个线程需要获取锁时,它可以使用spin_lock函数来锁定spinlock。如果锁已经被其他线程占用,该线程将进入自旋状态,不断检查锁的状态。当该线程获取到锁时,它可以将共享资源置于临界区并执行相关操作。在操作完成后,该线程可以使用spin_unlock函数释放锁。 spinlock机制适用于一些简单的同步场景,例如在并发访问共享资源时保护临界区代码。然而,对于一些复杂的同步需求,可能需要使用更高级的同步机制,如读写锁(rwlock)或条件变量(condition variable)。 总之,spinlock是一种轻量级的自旋锁机制,适用于简单的同步场景,具有较高的性能和灵活性。它适用于任何需要保护的临界区代码,而不仅仅是用于进程之间的同步。在使用spinlock时,需要注意避免死锁和过度自旋等问题。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值