乐观锁与悲观锁
一、概念介绍:
乐观锁(Optimistic Concurrency Control,缩写“OCC”),又叫做乐观并发控制,可以参考维基百科-乐观并发控制:
(https://zh.wikipedia.org/wiki/%E4%B9%90%E8%A7%82%E5%B9%B6%E5%8F%91%E6%8E%A7%E5%88%B6)
操作数据时非常乐观,认为别人不会同时修改数据。
因此乐观锁不会上锁,只是在执行更新的时候判断一下在此期间别人是否修改了数据;
适用场景:
当竞争不激烈 (出现并发冲突的概率小)时,乐观锁更有优势,
因为悲观锁会锁住代码块或数据,其他线程无法同时访问,影响并发,
而且加锁和释放锁都需要消耗额外的资源。
悲观锁(Pessimistic Concurrency Control,缩写“PCC”),又叫悲观并发控制,参考维基百科-悲观并发控制:
https://zh.wikipedia.org/wiki/%E6%82%B2%E8%A7%82%E5%B9%B6%E5%8F%91%E6%8E%A7%E5%88%B6
悲观锁在操作数据时比较悲观,认为别人会同时修改数据。
因此操作数据时直接把数据锁住,直到操作完成后才会释放锁;
上锁期间其他人不能修改数据,以便保证最大程度的独占性。
适用场景:
悲观并发控制主要用于数据竞争激烈的环境,
以及发生并发冲突时使用锁保护数据的成本要低于回滚事务的成本的环境中。
因为乐观锁在执行更新时频繁失败,需要不断重试,浪费CPU资源。
备注:对于悲观锁来说,使用比较简单,只需要在使用的时候,加锁和解锁即可,这里不做详细介绍,Go里面的sync便是悲观锁的典型代表。
二、乐观锁的使用
对于乐观锁而言,主要有两种使用方法,一种是CAS,一种是版本号控制。
1. CAS
比较并交换(compare and swap, CAS),是原子操作的一种,可用于在多线程编程中实现不被打断的数据交换操作,从而避免多线程同时改写某一数据时由于执行顺序不确定性以及中断的不可预知性产生的数据不一致问题。
CAS操作包括了3个操作数:
1.需要读写的内存位置(V)
2.进行比较的预期值(A)
3.拟写入的新值(B)
操作逻辑:
如果内存位置V的值等于预期的A值,则将该位置更新为新值B,
否则不进行任何操作。许多CAS的操作是自旋的:如果操作不成功,会一直重试,
直到操作成功为止。
备注:CAS中的比较和交换两个操作,是由CPU支持的原子操作,其原子性是在硬件层面进行保证的。
go中实现CAS的例子:
// CompareAndSwapUint32 executes the compare-and-swap operation for a uint32 value.
func CompareAndSwapUint32(addr *uint32, old, new uint32) (swapped bool)
在:/ src / runtime / internal / atomic / asm_amd.s文件中
TEXT runtime∕internal∕atomic·Cas64(SB), NOSPLIT, $0-25
MOVQ ptr+0(FP), BX
MOVQ old+8(FP), AX
MOVQ new+16(FP), CX
LOCK // 可以锁住总线保证多次内存操作的原子性
CMPXCHGQ CX, 0(BX) // 如果AX与BX相等,则CX送BX且ZF置1;否则BX送CX,且ZF清0
SETEQ ret+24(FP)
RET
go中的sync/automic 就是采用了乐观锁实现的。
CAS的主要缺点:
1.在竞争激烈的时候,每个线程/协程都是自旋的,影响效率。
2.无法处理ABA的问题,例如:协程1和协程2
1)协程1读取数据为A
2)协程2读取数据为A,并改成了B
3)协程1对数据进行CAS操作,
备注:虽然还是能修改数据,但是实际上当中已经被修改过多次了,特别是涉及到栈的操作的时候,就会出问题。
2.版本号控制
版本号机制的基本思路是在数据中增加一个字段version,表示该数据的版本号,每当数据被修改,版本号加1。当某个线程查询数据时,将该数据的版本号一起查出来;当该线程更新数据时,判断当前版本号与之前读取的版本号是否一致,如果一致才进行操作。当然这里的版本号也可以是时间戳或其他的字段。
(图片来自:https://www.cnblogs.com/dugo/p/12195135.html)
三、参考资料
https://www.cnblogs.com/kismetv/p/10787228.html
https://www.cnblogs.com/dugo/p/12195135.html
https://zhuanlan.zhihu.com/p/159334753
- 用Python实现微信接口(一)
- MYSQL常见错误及其解决方式
- 用Python实现微信接口(二)
- (63) 实用序列化: JSON/XML/MessagePack / 计算机程序的思维逻辑
- Quartz作业调度框架
- Oracle系统表整理+常用SQL语句收集
- Webpack 实用技巧高效实战
- oracle表空间不足相关问题解决办法
- 手工打造分布式爬虫
- (64) 常见文件类型处理: 属性文件/CSV/EXCEL/HTML/压缩文件 / 计算机程序的思维逻辑
- org.apache.struts2.dispatcher.ng.filter.StrutsPrepareAndExecuteFilter与org.apache.struts2.dispatcher.
- Python协程演进过程
- Android TV开发简介
- (59) 文件和目录操作 / 计算机程序的思维逻辑
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法