queued spinlock

ticket spinlock巧妙的解决了锁的公平性问题,但它在锁竞争方面还不够完美,linux-4.2内核引入了queued spinlock。

queued spinlock由Waiman Long和Perter Zijlstra 发起,补丁集经过了16个版本,并入了主线。

https://lkml.org/lkml/2015/4/24/631

https://lore.kernel.org/lkml/20140310155543.428990961@infradead.org/

前面介绍的ticket spinlock锁机制不是挺好的么,为啥又搞一个queue spinlock。它有什么缺点呢?

NUMA & CPU Cache Coherency

在当今普遍的SMP多核处理器架构和NUMA系统中,ticket spinlock存在一个比较严重的性能问题:由于多个CPU线程均在同一个共享变量lock.val上自旋,而申请和释放锁的时候必须对lock.val进行修改,这将导致所有参与排队自旋锁操作的处理器的缓存变得无效。如果排队自旋锁竞争比较激烈的话,频繁的缓存同步操作会导致繁重的系统总线和内存的流量,从而大大降低了系统整体的性能。

如下,ticket锁基本实现:

struct spinlock_t {union {atomic_t val;struct {int current_ticket;int next_ticket;}}
}void spin_lock(spinlock_t *lock)
{int t;t = atomic_fetch_and_inc (&lock->next_ticket);while (t != lock->current_ticket); /* spin */
}void spin_unlock(spinlock_t *lock)
{lock->current_ticket++;  //对变量val写入
}

多CPU竞争条件下,当线程调用spin_unlock释放锁,共享变量lock.val会被刷新。而其他CPU上的等待者必须刷新自己的cache才能看到最新值,这导致了所有其他等待CPU的cache line失效,可想而知,当遇到频繁锁竞争场景,必定会造成共享变量val在多个CPU的cache上弹跳,从而导致性能下降。

MCS lock

在介绍qspinlock之前,必须要先了解MCS lock,因为qspinlock是基于MCS lock设计的,MCS lock是一种解决多CPU并发竞争的自旋锁实现方法。

MCS单词啥意思?没啥意思!其实就是这两位作者的简称...,John M. Mellor-Crummey and Michael L. Scott,所以MCS并不是一个算法名称,也和自旋锁本身没啥关系,so 神奇!

MCS lock设计思想

每个锁的等待者,在本CPU上自旋(访问本地变量),而不是全局的spinlock变量,而当持有锁线程释放锁时,由锁的释放者更新下一个等待者的本地自旋变量,完成通知与同步。MCS锁可以消除锁所经历的大部分缓存弹跳,尤其是在多竞争的情况下。

http://locklessinc.com/articles/locks/

MCS锁在学术界实现了很多种变种,我们来看一个最简单的伪代码实现,只为表达MCS锁的核心设计思想。

/** struct mcs_node结构体用于描述本地节点,mcs_node中包含2个变量,next指针用于指向下一个等待者,而另外一个变量,则用于自旋锁的自旋。* 很明显,mcs_node结构可以让所有等待者变成一个单向链表。*/
struct mcs_node {struct mcs_node *next;   /* 指向下一个等待者 */int is_locked;           /* 本地自旋变量 */
}/** 全局spinlock中含有一个mcs_node指针,指向最后一个锁的申请者。而当锁处于空闲时,该指针为NULL。*/
struct spinlock_t {mcs_node *queue;    /* 等待者队列 */
}//加锁函数
mcs_spin_lock(spinlock_t *lock, mcs_node *my_node)
{my_node->next = NULL;               -----[1]mcs_node *predecessor = fetch_and_store(lock->queue, my_node);  -----[2]if (predecessor != NULL) {          -----[3]my_node->is_locked = true;      -----[4]predecessor.next = my_node;     -----[5]while (my_node->is_locked)      -----[6]cpu_relax();}
}//放锁函数
mcs_spin_unlock(spinlock_t *lock, mcs_node *my_node)
{if (my_node->next == NULL) {          -----[7]if (compare_and_swap(lock->queue, my_node, NULL) {  ----[8]return;}else {while (my_node->next == NULL) -----[9]cpu_relax();}}my_node->next->is_locked = false;     -----[10]
}

spin_lock()解读:

1、当一个线程申请锁时,会传入一个本地变量my_node,并默认将其next域置为NULL,认为我是最后一个申请者。

2、这里使用fetch_and_store原子语句,先将全局spinlock->queue指向自己(my_node),代表我是最后的申请者,同时该函数返回当前持锁人predecessor。

3、若predecessor==NULL,则说明无人持锁,该线程即拿到锁,直接return。若predecessor!=NULL,则说明遇到竞争,需要自旋等待。

4-5、将本地变量my_node->is_locked置为true,同时将当前持锁者的next域指向自己。

6、在本地变量上疯狂自旋。

spin_unlock()解读:

7、若my_node->next等于NULL,说明没有锁竞争,需要将全局锁spinlock->queue置空。

8、使用原子指令,将全局lock->queue置空,如果成功置则return。

9、如果没有置空成功,说明有人已经抢先一步将spinlock->queue赋值,所以下一个锁的申请者一定会排在我的后面,这里等待my_node->next域被赋值完成。

10、将下一个等待者的is_locked置为false,完成解锁。

如此MCS lock介绍完成,linux借鉴了MCS锁的设计思想,实现了queued spinlock自旋锁。但你会发现,该MCS锁代码并不能与linux完美融合,其主要原因有两个:

1、当spin_lock及spin_unlock时,要额外传递了一个node参数。无法和现有spinlock API兼容。

2、若将mcs_node结构移至spinlock结构中,可以变为单参数,但是自旋锁经常被嵌入到许多内核结构中,其中一些(尤其是struct page等结构)不能容忍大小的增加。

内核queued spinlock

实现queued spinlock远比你想象的要复杂的多,linux内核实现时,考虑了在无竞争,双cpu竞争,及多个cpu竞争等各种复杂情况,并对其分别优化。

如果你看过queue spinlock代码,你会发现当一个CPU申请spin_lock时,在spin_lock函数会有多个spin位置,根据当前锁的不同情况,CPU可能在不同的位置spin。并且同时spin_lock也会有多个返回出口,这都是为了优化spinlock多核竞争而编写。由于queue spinlock实现稍有复杂,下次再聊。

参考:

https://lwn.net/Articles/590243/

https://www.cs.rochester.edu/~scott/papers/1991_TOCS_synch.pdf

https://0xax.gitbooks.io/linux-insides/content/SyncPrim/linux-sync-2.html

linux锁机制:queued spinlock相关推荐

  1. Linux 锁机制(3)之自旋锁

    Linux 锁机制(3)之自旋锁 1. 自旋锁 1.1 两种锁 1.2 自旋锁 1.3 自旋名字来源:自旋锁一直循环等待,直到获取锁为止. 1.4 自旋锁优点: 2 自旋锁特点/使用: 2.1 临界区 ...

  2. Linux 2.6内核中新的锁机制--RCU [转]

    2005 年 7 月 01 日 本文详细地介绍了 Linux 2.6 内核中新的锁机制 RCU(Read-Copy Update) 的实现机制,使用要求与典型应用. 一. 引言 众所周知,为了保护共享 ...

  3. 大话Linux内核中锁机制之原子操作、自旋锁【转】

    转自:http://blog.sina.com.cn/s/blog_6d7fa49b01014q7p.html 多人会问这样的问题,Linux内核中提供了各式各样的同步锁机制到底有何作用?追根到底其实 ...

  4. 从自旋锁、睡眠锁、读写锁到 Linux RCU 机制讲解

    总结一下 O/S 课程里面和锁相关的内容. 本文是 6.S081 课程的相关内容总结回顾结合 Real World 的 Linux 讲解各种锁和 RCU lock free 机制原理, 前置知识是基本 ...

  5. Linux 2.6内核中新的锁机制--RCU

    转载自: Linux 2.6内核中新的锁机制--RCU 一. 引言 众所周知,为了保护共享数据,需要一些同步机制,如自旋锁(spinlock),读写锁(rwlock),它们使用起来非常简单,而且是一种 ...

  6. Linux内核中锁机制之完成量、互斥量

    在上一篇博文中笔者分析了关于信号量.读写信号量的使用及源码实现,接下来本篇博文将讨论有关完成量和互斥量的使用和一些经典问题. 八.完成量 下面讨论完成量的内容,首先需明确完成量表示为一个执行单元需要等 ...

  7. linux 信号量锁 内核,Linux内核中锁机制之信号量、读写信号量

    在上一篇博文中笔者分析了关于内存屏障.读写自旋锁以及顺序锁的相关内容,本篇博文将着重讨论有关信号量.读写信号量的内容. 六.信号量 关于信号量的内容,实际上它是与自旋锁类似的概念,只有得到信号量的进程 ...

  8. 读写自旋锁 linux,boost是否像Linux一样提供读写自旋锁机制?

    用户态 spinlock 这个东西,其实没多大用.(其实在内核态用的地方也很少,基本上依赖 scheduler 的代码都不能用.) 如果你看过 Linux kernel 代码,你会知道在 disabl ...

  9. openVswitch(OVS)源代码之linux RCU锁机制分析

    前言 本来想继续顺着数据包的处理流程分析upcall调用的,但是发现在分析upcall调用时必须先了解linux中内核和用户空间通信接口Netlink机制,所以就一直耽搁了对upcall的分析.如果对 ...

最新文章

  1. java重新组合_Java 合并Word文档
  2. C++继承机制下的析构函数
  3. 【云炬大学生创业基础笔记】第1章第2节测试
  4. 关于resolve非泛型方法不能与类型实参一起使用
  5. 2008 读第一本书
  6. ADO 动态链接数据库
  7. 《你不知道的JavaScript》-- 精读(一)
  8. iPhone企业应用实例分析之二:程序处理流程
  9. android 音效,音效  |  Android 开源项目  |  Android Open Source Project
  10. 直流稳压电源设计(单相)_电力电子课程设计
  11. 中国5级省市编码爬去整理(统计用区划和城乡划分代码)
  12. navicat建mysql数据库密码_Navicat修改MySQL数据库密码的多种方法
  13. 链表排序python
  14. 如何用运营思维,搭建会员运营体系
  15. 如何打开mysql数据库?
  16. 安卓和IOS推广技巧汇总,app安卓推广、ios推广aso优化
  17. 如何为4万名订阅者编写自动令牌空投脚本
  18. java centos 缩略图_使用 Nginx 的 image_filter 模块来构建动态缩略图服务器
  19. 使用决策树和随机森林预测NBA获胜球队
  20. ServletConext和Aplication的区别

热门文章

  1. 重阳节计算机培训,重阳节日记
  2. Dialer 联系人加载
  3. 依存句法分析:基于图的依存句法分析、基于转移的依存句法分析、基于神经网络的依存句法分析
  4. 2019python二级考试报名时间_2019湖北计算机二级考试(时间 科目 报名方法)
  5. SQL Server索引的创建与维护
  6. 万字长文剖析Dapp生态,《2019 Dapp行业报告》重磅发布
  7. win10开启护眼模式
  8. 【数理逻辑】命题逻辑 ( 命题逻辑推理正确性判定 | 形式结构是永真式 - 等值演算 | 从前提推演结论 - 逻辑推理 )
  9. 人工智能的几个研究方向
  10. axure9总是崩_有没有人和我一样,觉得axure9 很难用