Ticket Lock, CLH Lock, MCS Lock
如果不用OS提供的mutex,我们该如何实现互斥锁?(不考虑重入的情况)
1. naive lock
最简单的想法是,搞一个volatile类型的共享变量flag,值可以是flase(无锁)或者true(有锁),竞争线程监听flag,一旦发现flag为false,那么尝试cas更新flag为true,更新成功则说明占有了这个锁,更新失败说明临界区已经被其他线程占领,继续监听flag并尝试更新。占有锁的线程退出的时候,将flag修改为false,表示释放锁。
volatile boolean flag = false;
void lock() {
while (!cas(flag, false, true)) {//返回true:占锁成功,返回false:占锁失败,继续循环尝试
}
}
void unlock() {
flag = false;
}
这样做有个问题是无法保证公平性,可能有的倒霉蛋空转了一辈子也无法cas成功,无法做到按竞争线程先来后到的次序占有锁。
2. Ticket Lock
为了提供公平,有人发明了Ticket Lock
线程想要竞争某个锁,需要先领一张ticket,然后监听flag,发现flag被更新为手上的ticket的值了,才能去占领锁
就像是在医院看病一样,医生就是临界区,病人就是线程,病人挂了号领一张单子,单子上写了一个独一无二的号码,病人等的时候就看屏幕,屏幕上显示到自己的号码了,才能进去找医生。
AtomicInteger ticket = new AtomicInteger(0);
volatile int flag = 0; void lock() {
int my_ticket = ticket.getAndIncrement();//发号必须是一个原子操作,不能多个线程拿到同一个ticket
while (my_ticket != flag) { }
} void unlock() {
flag++;
}
现在公平性的问题没有了,但是所有的线程都在监听flag变量,而且由于为了保证flag变量变化的可见性,它必须是volatile的。也就是说如果某个线程修改了flag变量,都会引起其他所有监听线程所在的core的对应于flag变量的cache line被设为invalid,那么这些线程下一次查询flag变量的时候,就必须从主存里取最新的flag数据了,由于主存带宽有限,这个开销较为昂贵(与监听线程数成正比)。
3. CLH Lock
为了减少缓存一致性带来的开销,CLH Lock被发明了。
ps,CLH实际上是指三个人:Craig, Landin, and Hagersten
CLH锁的核心思想是,1. 竞争线程排队 2. 监听变量拆分
CLH锁维护了一个链表waitingList的head与tail,其节点定义如下:
static class Node {
volatile boolean flag;//true:当前线程正在试图占有锁或者已经占有锁,false:当前线程已经释放锁,下一个线程可以占有锁了
Node prev;//监听前一个节点的flag字段
}
初始时需要定义一个dummy节点(dummpy.flag == true, dummy.prev == null),head == tail == dummy
当有线程想要获取锁时,先创建一个链表节点node,然后将node挂载在waitingList的尾部(尝试cas(tail, oldTail, node),如果成功将node.prev更新为oldTail,失败则重试)
然后这个线程就监听node.prev.flag,什么时候node.prev.flag == false了,说明node的前一个节点对应的线程已经释放了锁,本线程此时可以安全的占有锁了
释放锁的时候,将对应的node.flag修改为false即可。
实现代码如下(相当粗糙,意会即可):
public class CLHLock {
volatile Node head, tail;//waitingList
public CLHLock() {
head = tail = Node.DUMMY;
}
public Node lock() {
//lock-free的将node添加到waitingList的尾部
Node node = new Node(true, null);
Node oldTail = tail;
while (!cas(tail, oldTail, node)) {
oldTail = tail;
}
node.setPrev(oldTail);
while (node.getPrev().isLocked()) {//监听前驱节点的locked变量
}
return node;
}
public void unlock(Node node) {
node.setLocked(false);
}
static class Node {
public Node(boolean locked, Node prev) {
this.locked = locked;
this.prev = prev;
}
volatile boolean locked;//true:当前线程正在试图占有锁或者已经占有锁,false:当前线程已经释放锁,下一个线程可以占有锁了
Node prev;//监听前一个节点的locked字段
public boolean isLocked() {
return locked;
}
public void setLocked(boolean locked) {
this.locked = locked;
}
public Node getPrev() {
return prev;
}
public void setPrev(Node prev) {
this.prev = prev;
}
public static final Node DUMMY = new Node(false, null);
}
}
这样做可以极大的减少缓存一致性协议所带来的开销。
CLH锁的变种被应用于Java J.U.C包下的AbstractQueuedSynchronizer
4. MCS锁
CLH锁并不是完美的,因为每个线程都是在前驱节点的locked字段上自旋,而在NUMA体系中,有可能多个线程工作在多个不同的socket上的core里。如果前驱节点的内存跟监听线程的core距离过远,会有性能问题。
于是MCS锁诞生了
ps,MCS也是人名简写:John M. Mellor-Crummey and Michael L. Scott
MCS与CLH最大的不同在于:CLH是在前驱节点的locked域上自旋,MCS是在自己节点上的locked域上自旋。
具体的实现是,前驱节点在释放锁之后,会主动将后继节点的locked域更新。
也就是把多次对远端内存的监听 + 一次对本地内存的更新,简化成了多次对本地内存的监听 + 一次对远端内存的更新。
具体的实现如下
public class MCSLock {
volatile Node head, tail;//waitingList
public MCSLock() {
head = tail = null;
}
public Node lock() {
//lock-free的将node添加到waitingList的尾部
Node node = new Node(true, null);
Node oldTail = tail;
while (!cas(tail, oldTail, node)) {
oldTail = tail;
}
if (null == oldTail) {//如果等待列表为空,那么获取锁成功,直接返回
return node;
}
oldTail.setNext(node);
while (node.isLocked()) {//监听当前节点的locked变量
}
return node;
}
public void unlock(Node node) {
if (node.getNext() == null) {
if (cas(tail, node, null)) {//即使当前节点的后继为null,也要用cas看一下队列是否真的为空
return;
}
while (node.getNext() != null) {//cas失败,说明有后继节点,只是还没更新前驱节点的next域,等前驱节点看到后继节点后,即可安全更新后继节点的locked域
}
}
node.getNext().setLocked(false);
}
static class Node {
public Node(boolean locked, Node next) {
this.locked = locked;
this.next = next;
}
volatile boolean locked;//true:当前线程正在试图占有锁或者已经占有锁,false:当前线程已经释放锁,下一个线程可以占有锁了
Node next;//后继节点
public boolean isLocked() {
return locked;
}
public void setLocked(boolean locked) {
this.locked = locked;
}
public Node getNext() {
return next;
}
public void setNext(Node next) {
this.next = next;
}
}
参考资料
Ticket Lock, CLH Lock, MCS Lock的更多相关文章
- Synchronized和Lock, 以及自旋锁 Spin Lock, Ticket Spin Lock, MCS Spin Lock, CLH Spin Lock
Synchronized和Lock synchronized是一个关键字, Lock是一个接口, 对应有多种实现. 使用synchronized进行同步和使用Lock进行同步的区别 使用synchro ...
- ubuntu 常见错误--Could not get lock /var/lib/dpkg/lock
ubuntu 常见错误--Could not get lock /var/lib/dpkg/lock 通过终端安装程序sudo apt-get install xxx时出错:E: Could not ...
- ubuntu常见错误--could not get lock /var/lib/dpkg/lock -open
最近研究ubuntu,用apt-get命令安装一些软件包时,总报错:E:could not get lock /var/lib/dpkg/lock -open等 出现这个问题的原因可能是有另外一个程序 ...
- 【ubuntu 】常见错误--Could not get lock /var/lib/dpkg/lock
ubuntu 常见错误--Could not get lock /var/lib/dpkg/lock 通过终端安装程序sudo apt-get install xxx时出错: E: Could not ...
- apt-get报错could not get lock /var/lib/dpkg/lock -open等
用apt-get命令安装一些软件包时,总报错:E:could not get lock /var/lib/dpkg/lock -open等 出现这个问题的原因可能是有另外一个程序正在运行,导致资源被锁 ...
- ubuntu常见错误--Could not get lock /var/lib/dpkg/lock解
通过终端安装程序sudo apt-get install xxx时出错: E: Could not get lock /var/lib/dpkg/lock - open (11: Reso ...
- 14.4.9 Configuring Spin Lock Polling 配置Spin lock 轮询:
14.4.9 Configuring Spin Lock Polling 配置Spin lock 轮询: 很多InnoDB mutexes 和rw-locks 是保留一小段时间,在一个多核系统, 它可 ...
- ubuntu常见错误--Could not get lock /var/lib/dpkg/lock解决
通过终端安装程序sudo apt-get install xxx时出错: E: Could not get lock /var/lib/dpkg/lock - open (11: Resource t ...
- ubuntu 16.04常见错误--Could not get lock /var/lib/dpkg/lock解决
我的博客 ubuntu常见错误--Could not get lock /var/lib/dpkg/lock解决 通过终端安装程序sudo apt-get install xxx时出错: E: Cou ...
随机推荐
- (HTML)A标签伪元素选择器的继承关系
①如果a:link{}也存在,那么不管a{}放到哪里,a{}和a:link{}冲突的属性都会采用a:link{}的,不冲突的属性若存在a{}中,会被a:link{}. a:visited{} .a:h ...
- Android通过AIDL和反射调用系统拨打电话和挂断电话
首先在项目中添加ITelephony.aidl文件,我的如下: /* * Copyright (C) 2007 The Android Open Source Project * * Licensed ...
- Codeforces 678F Lena and Queries
题意: 你有一个点集,有三种操作: 往集合里插入一个点\((x, y)\) 从集合中删除第\(i\)次操作插入的点 对于给出的\(q\),询问点集中\(x \cdot q + y\)的最大值 分析: ...
- 2019腾讯暑期实习面试(offer)前端
最近在忙着准备找实习,所以没有更新之前的文章. 不过所幸功夫不负有心人,我拿到了腾讯的offer. 这里分享一下面试的经验. 简介 本人双非本科,普通学生一枚. 面的是腾讯的Web前端开发. 整个面试 ...
- leetcode 【 Best Time to Buy and Sell Stock III 】python 实现
题目: Say you have an array for which the ith element is the price of a given stock on day i. Design a ...
- leetcode 【 Maximum Subarray 】python 实现
题目: Find the contiguous subarray within an array (containing at least one number) which has the larg ...
- Python 拓展之特殊函数(lambda 函数,map 函数,filter 函数,reduce 函数)
写在之前 今天给大家介绍几个比较特殊的函数,他们具有函数式编程的特点,有人将它们视为 Python 可进行 "函数式编程" 的见证,至于什么是函数式编程,不是本篇文章的重点,感兴趣 ...
- Qt数据库查询之事务操作
在做借书系统的时候,用到了事务操作,不会使用qt中事务操作怎么写,查了一些博客帖子,并不起作用,后来发现,在进行事务成功判断时,出现问题,正确代码如下 if(QSqlDatabase::databas ...
- 服务器(centos7)用nginx挂出多个网站的配置
前提: 装好环境的centos7系统(其他版本也行),装好ngnix: 推荐:http://www.cnblogs.com/alsy/p/5296244.html 把你的项目上传到服务器上(可以用Xf ...
- Summary—【base】(CSS)
CSS知识点 1. CSS又被称为层叠样式表 2. CSS在html中的语法 a) <style> 选择器{键:值;} </style> b) css中最后一个键值可以不写分号 ...