SpinLock 自旋锁, CAS操作(Compare & Set) ABA Problem
SpinLock 自旋锁
spinlock 用于CPU同步, 它的实现是基于CPU锁定数据总线的指令.
当某个CPU锁住数据总线后, 它读一个内存单元(spinlock_t)来判断这个spinlock 是否已经被别的CPU锁住.
如果否, 它写进一个特定值, 表示锁定成功, 然后返回.
如果是, 它会重复以上操作直到成功, 或者spin次数超过一个设定值.
锁定数据总线的指令只能保证一个机器指令内, CPU独占数据总线.
单CPU当然能用spinlock, 但实现上无需锁定数据总线.
spinlock在锁定的时候,如果不成功,不会睡眠,会持续的尝试,
单cpu的时候spinlock会让其它process动不了.
自旋锁是一种保护数据结构或代码片段的原始方式,
在某个时刻只允许一个进程访问临界区内的代码。
一般spinlock实现会有一个参数限定最多持续尝试次数.
超出后, spinlock放弃当前time slice. 等下一次机会.
有三果问题不清楚
1,单CPU内核中,假设好几个内核线程用spinlock共享一块数据,一个时间只有一个线程得到锁,
那是不是其它没有得到锁的线程都在疯狂的自旋中?
2,内核里似乎到处都有spinlock,在单CPU系统下spinlock主要用来做什么呢?
也能用来当数据同步的锁来用?
3,用户空间有spinlock吗?
1. Yes. 但spinlock只适用于short lock.
2. 单CPU, 或SMP, 都是同步锁
3. Yes
1. 通常如果一个线程没有得到锁会调用 sleep() 或者 yield() 之类的函数
让调度器重新进行调度,不会疯狂自旋的。
2. 自旋锁是一种很低级的操作,是为了实现资源的互斥而不是同步,
在单 CPU 中其实可以用关闭中断的方法代替自旋锁,
在多 CPU 中自旋锁必须要实现,这如前面一个朋友所说的,自旋锁需要锁总线,
你可以查看 X86 的 lock 指令和 compare and exchange 指令或者 test and set 指令得到更多的信息。
3. 通常在用户空间不使用自旋锁,在用户空间通常使用 Mutex 互斥,Semaphore 同步。
自旋锁有可能用于实现 Mutex 和 Semaphore.
CAS操作——Compare & Set
或是 Compare & Swap,现在几乎所有的CPU指令都支持CAS的原子操作,
X86下对应的是 CMPXCHG 汇编指令。
有了这个原子操作,我们就可以用其来实现各种无锁(lock free)的数据结构。
这个操作用C语言来描述就是下面这个样子:(代码来自Wikipedia的Compare And Swap词条)
意思就是说,看一看内存*reg里的值是不是oldval,如果是的话,则对其赋值newval。
int compare_and_swap (int* reg, int oldval, int newval)
{
int old_reg_val = *reg;
if(old_reg_val == oldval)
*reg = newval;
return old_reg_val;
}
这个操作可以变种为返回bool值的形式(返回 bool值的好处在于,可以调用者知道有没有更新成功)
bool compare_and_swap (int*accum, int*dest, int newval)
{
if( *accum == *dest )
{
*dest = newval;
return true;
}
return false;
}
与CAS相似的还有下面的原子操作:(这些东西大家自己看Wikipedia吧)
- Fetch And Add,一般用来对变量做 +1 的原子操作
- Test-and-set,写值到某个内存位置并传回其旧值。汇编指令BST
- Test and Test-and-set,用来低低Test-and-Set的资源争夺情况
cmpxchg是汇编指令
IF accumulator == DEST THEN
ZF <- 1; // ACC - DEST == 0
DEST <- SRC;
ELSE
ZF <- 0; // ACC - DEST != 0
accumulator <- DEST;
FI;
CMPXCHG CX,DX
首先进行CMP操作,这个操作就是进行减运算,但不保存结果,只是影响标志位ZF的。AX CX相减为0,ZF置位为1。
CMPXCHG隐含使用EAX寄存器,根据首操作数的位数确定EAX的位数,就是根据CX来确定是AX,
如果是CL,则就是AL,根据CMP结果进行XCHG,相等则第2操作数送到第1操作数,不等则第1操作数送EAX或AX或AL。
象这种隐含使用其他寄存器的指令还有不少。对于哪种操作影响标志位也需要慢慢熟悉。
其实在很久前,CMPXCHG指令是很标准的,它规定第2个操作数必须是EAX/AX/AL,
这样就简单了,先比较2个操作数,如果相等,ZF置1,第2操作数送第1操作数,
如果不等,ZF清0,第1操作数送第2操作数。很标准的“比较交换”。
只不过后来对第2操作数就不做限制必须是EAX/AX/AL了,变成隐含使用
CAS 操作包含三个操作数 —— 内存位置(V)、预期原值(A)和新值(B)。
如果内存位置的值与预期原值相匹配,那么处理器会自动将该位置值更新为新值。
否则,处理器不做任何操作。无论哪种情况,它都会在 CAS 指令之前返回该位置的值。
(在 CAS 的一些特殊情况下将仅返回 CAS 是否成功,而不提取当前值。)
CAS 有效地说明了“我认为位置 V 应该包含值 A;如果包含该值,则将 B 放到这个位置;
否则,不要更改该位置,只告诉我这个位置现在的值即可。”
通常将 CAS 用于同步的方式是从地址 V 读取值 A,执行多步计算来获得新值 B,然后使用 CAS 将 V 的值从 A 改为 B。
如果 V 处的值尚未同时更改,则 CAS 操作成功。
类似于 CAS 的指令允许算法执行读-修改-写操作,而无需害怕其他线程同时修改变量,
因为如果其他线程修改变量,那么 CAS 会检测它(并失败),算法可以对该操作重新计算。
ABA Problem
CAS:对于内存中的某一个值V,提供一个旧值A和一个新值B。
如果提供的旧值V和A相等就把B写入V。这个过程是原子性的。
CAS执行结果要么成功要么失败,对于失败的情形下一班采用不断重试。或者放弃。
ABA:如果另一个线程修改V值, 假设原来是A,先修改成B,再修改回成A。
当前线程的CAS操作无法分辨当前V值是否发生过变化。
关于ABA问题我想了一个例子:
在你非常渴的情况下你发现一个盛满水的杯子,你一饮而尽。之后再给杯子里重新倒满水。
然后你离开,当杯子的真正主人回来时看到杯子还是盛满水,他当然不知道是否被人喝完重新倒满。
解决这个问题的方案的一个策略是每一次倒水假设有一个自动记录仪记录下,
这样主人回来就可以分辨在她离开后是否发生过重新倒满的情况。这也是解决ABA问题目前采用的策略。
CAS的ABA问题
所谓ABA(见维基百科的ABA词条),问题基本是这个样子:
- 进程P1在共享变量中读到值为A
- P1被抢占了,进程P2执行
- P2把共享变量里的值从A改成了B,再改回到A,此时被P1抢占。
- P1回来看到共享变量里的值没有被改变,于是继续执行。
虽然P1以为变量值没有改变,继续执行了,但是这个会引发一些潜在的问题。
ABA问题最容易发生在lock free 的算法中的,CAS首当其冲,因为CAS判断的是指针的地址。
如果这个地址被重用了呢,问题就很大了。
你拿着一个装满钱的手提箱在飞机场,此时过来了一个火辣性感的美女,然后她很暖昧地挑逗着你,并趁你不注意的时候, 把用一个一模一样的手提箱和你那装满钱的箱子调了个包,然后就离开了,你看到你的手提箱还在那,于是就提着手提箱去赶飞机去了。
这就是ABA的问题。
SpinLock 自旋锁, CAS操作(Compare & Set) ABA Problem的更多相关文章
- LiteOS:SpinLock自旋锁及LockDep死锁检测
摘要:除了多核的自旋锁机制,本文会介绍下LiteOS 5.0引入的LockDep死锁检测特性. 2020年12月发布的LiteOS 5.0推出了全新的内核,支持SMP多核调度功能.想学习SMP多核调度 ...
- SpinLock(自旋锁)
SpinLock(自旋锁) SpinLock 结构是一个低级别的互斥同步基元,它在等待获取锁时进行旋转. 在多核计算机上,当等待时间预计较短且极少出现争用情况时,SpinLock 的性能将高于其他类型 ...
- 并发系列2:Java并发的基石,volatile关键字、synchronized关键字、乐观锁CAS操作
由并发大师Doug Lea操刀的并发包Concurrent是并发编程的重要包,而并发包的基石又是volatile关键字.synchronized关键字.乐观锁CAS操作这些基础.因此了解他们的原理对我 ...
- spinlock自旋锁de使用
Linux内核中最常见的锁是自旋锁.一个自旋锁就是一个互斥设备,它只能有两个值:"锁定"和"解锁".如果锁可用,则"锁定"位被设置,而代码继 ...
- Pthread spinlock自旋锁
锁机制(lock) 是多线程编程中最常用的同步机制,用来对多线程间共享的临界区(Critical Section) 进行保护. Pthreads提供了多种锁机制,常见的有:1) Mutex(互斥量): ...
- 使用C++11原子量实现自旋锁
一.自旋锁 自旋锁是一种基础的同步原语,用于保障对共享数据的互斥访问.与互斥锁的相比,在获取锁失败的时候不会使得线程阻塞而是一直自旋尝试获取锁.当线程等待自旋锁的时候,CPU不能做其他事情,而是一直处 ...
- 锁、CAS操作和无锁队列的实现
https://blog.csdn.net/yishizuofei/article/details/78353722 锁的机制 锁和人很像,有的人乐观,总会想到好的一方面,所以只要越努力,就会越幸运: ...
- 非阻塞同步机制与CAS操作
锁的劣势 Java在JDK1.5之前都是靠synchronized关键字保证同步的,这种通过使用一致的锁定协议来协调对共享状态的访问,可以确保无论哪个线程 持有守护变量的锁,都采用独占的方式来访问这些 ...
- Linux内核同步:自旋锁
linux内核--自旋锁的理解 自旋锁:如果内核配置为SMP系统,自旋锁就按SMP系统上的要求来实现真正的自旋等待,但是对于UP系统,自旋锁仅做抢占和中断操作,没有实现真正的“自旋”.如果配置了CON ...
随机推荐
- 开发者必读jQuery Mobile入门教程
你每天都会对着它讲话,和它玩游戏,用它看新闻——没错,它就是你裤兜里的智能手机.android,黑莓还是iphone?为了让你清楚意识到究竟哪些才算是智能手机,我在下面总结了一个智能手机系统/设备的列 ...
- Ajax请求内嵌套Ajax请求的方法
前段时间做项目,需要把全国省市的两个XML文件整合成一个JSON格式的数据,手写的话觉得数据太多了,而且容易出错,于是就想到了用Ajax嵌套的方法来解决,就想平时用Ajax的方法直接嵌套,都会先读出外 ...
- C# GDI+学习笔记1
前言 本文是学习C# GDI+系列的第一篇文章,简单的介绍了GDI+的一些基本绘图内容,比较粗糙.但本文主要是让大家简单的回顾一下GDI+的基本概念.本篇文章的参考代码请在此下载 . GDIPTes ...
- [Everyday Mathematics]20150228
试证: $$\bex \int_0^\infty \sin\sex{x^3+\frac{\pi}{4}}\rd x =\frac{\sqrt{6}+\sqrt{2}}{4}\int_0^\infty ...
- 设置TextView控件的背景透明度和字体透明度
TextView tv = (TextView) findViewById(R.id.xx); 第1种:tv.setBackgroundColor(Color.argb(255, 0, 255, 0) ...
- 13、NFC技术:读写非NDEF格式的数据
MifareUltralight数据格式 将NFC标签的存储区域分为16个页,每一个页可以存储4个字节,一个可存储64个字节(512位).页码从0开始(0至15).前4页(0至3)存储了NFC标签相关 ...
- VC++中内存对齐
我们经常看到求 sizeof(A) 的值的问题,其中A是一个结构体,类,或者联合体. 为了优化CPU访问和优化内存,减少内存碎片,编译器对内存对齐制定了一些规则.但是,不同的编译器可能有不同的实现,本 ...
- 轻松学习Linux之认识内存管理机制
本文出自 "李晨光原创技术博客" 博客,谢绝转载!
- SharePoint咨询师之路:备份和恢复系列--制定备份计划
本来想研究下如何做数据库服务器的集群,然而突然被同事问起如何在部署SharePoint服务场的时候做备份和恢复的计划,就先来复习和研究一下. 本系列包括: 备份服务器场和配置 备份web和服务应用程序 ...
- MYSQL数据库重点:自定义函数、存储过程、触发器、事件、视图
一.自定义函数 mysql自定义函数就是实现程序员需要sql逻辑处理,参数是IN参数,含有RETURNS字句用来指定函数的返回类型,而且函数体必须包含一个RETURN value语句. 语法: 创建: ...