【协作式原创】查漏补缺之Golang中mutex源码实现(预备知识)
预备知识
CAS机制
1. 是什么
参考附录3
CAS 是项乐观锁技术,当多个线程尝试使用 CAS 同时更新同一个变量时,只有其中一个线程能更新变量的值,而其它线程都失败,失败的线程并不会被挂起,而是被告知这次竞争中失败,并可以再次尝试。
CAS 操作包含三个操作数 —— 内存位置(V)、预期原值(A)和新值(B)。
如果内存位置的值与预期原值相匹配,那么处理器会自动将该位置值更新为新值。否则,处理器不做任何操作。无论哪种情况,它都会在 CAS 指令之前返回该位置的值。(在 CAS 的一些特殊情况下将仅返回 CAS 是否成功,而不提取当前值。)
CAS 有效地说明了“我认为位置 V 应该包含值 A;如果包含该值,则将 B 放到这个位置;否则,不要更改该位置,只告诉我这个位置现在的值即可。”这其实和乐观锁的冲突检查 + 数据更新的原理是一样的。
扩展:
Q: 悲观锁和乐观锁的主要区别.
A:
乐观锁每次获取数据的时候,都不会担心数据被修改,所以每次获取数据的时候都不会进行加锁,但是在更新数据的时候需要判断该数据是否被别人修改过。
悲观锁在读取数据也会加锁,甚至只能有1个线程可以读取数据。
2. 图解CAS
讲解参考附录1的图解.
package main
var i int32 = 1
func main() {
    i++
}
- 反汇编查看第6行的汇编代码,可以发现和上图中C代码类似,也是对应3个底层汇编指令。(注意单个汇编指令不一定是原子操作,只是我们分析只考虑汇编指令之间进行中断和切换)
 
$ go tool compile -S main.go |grep main.go:6
        0x0012 00018 (main.go:6)        PCDATA  $2, $0
        0x0012 00018 (main.go:6)        PCDATA  $0, $0
        0x0012 00018 (main.go:6)        MOVL    "".i(SB), AX // 根据i的内存地址(addr1),加载i值到寄存器
        0x0018 00024 (main.go:6)        INCL    AX           // 寄存器执行自增1操作
        0x0019 00025 (main.go:6)        MOVL    AX, "".i(SB) // 寄存器的值更新给i对应的内存地址(addr1)
- 关键问题:
Q: 什么叫冲突
A: 内存实际值和携程预期值不等就是冲突。 就是携程发现内存实际值(11)!=old值(10)就是冲突,本来该携程是要对10进行加1操作的. 
3. Go中的CAS操作代码示例
讲解参考附录2的代码.
// CompareAndSwapInt64 executes the compare-and-swap operation for an int64 value.
func CompareAndSwapInt64(addr *int64, old, new int64) (swapped bool)
预备知识点: atomic.LoadInt64和atomic.StoreInt64,是一对原子操作,分别是读取和写入一个int。
TODO: 目前不清楚附录2中提到的错误写法有啥问题。
- 扩展
Q: 这里引出一个新的问题,既然CAS包含了Compare和Swap两个操作,它又如何保证原子性呢?或者问CompareAndSwapInt64函数为啥能保证原子性呢? 或者问atomic包中的函数怎么保证的原子性?
A: CAS是由CPU支持的原子操作,其原子性是在硬件层面进行保证的。至于硬件层面是怎么保证的参考该博客的后半部分 
4. cas机制的缺点
- 高并发情况下自旋消耗cpu.(也就是compare老是失败)
 - 只支持一个变量的原子操作,不支持多个变量(代码块)的原子操作. 代码块的原子操作,用sync.Mutex。
 - CAS有一种特殊情况:ABA。ABA是否会导致问题需要具体来分析: (参考附录4)
3.1 没有问题的case: 对i加一减一,导致Compare成功,其实是不会影响运行结果的。
3.2 有问题的case: Real-world examples for ABA in multithreading 
位操作和Go特殊语法iota
- &(与操作)
1&1=1,1&0=1,0&0=1 
func main() {
	var state int32
	mutexLocked := int32(1)
	mutexWoken := int32(2)
	// & 与运算,只有两者都是1结果才是1.
	fmt.Println(state&mutexLocked, state&mutexWoken)
	state = mutexLocked
	fmt.Println(state&mutexLocked, state&mutexWoken) // ...001 & ...001 = ...001
	state = mutexWoken
	fmt.Println(state&mutexLocked, state&mutexWoken) // ...010 & ...010 = ...010
}
//output
0 0
1 0
0 2
- 左移操作 a<<n: a左移n位
 
1<<0 = 1 = 1
1<<1 = 10 = 2
1<<2 = 100 = 4
- Golang特殊语法:iota
参考附录5 
const (
	mutexLocked      = 1 << iota // mutex is locked // 1左移iota(0)位赋值给变量 // iota = 0
	mutexWoken                   // 1左移iota(1)位赋值给变量 // iota = 1
	mutexStarving                // 1左移iota(2)位赋值给变量 // iota = 2
	mutexWaiterShift = iota      // iota = 3
)
func main() {
	fmt.Println(mutexLocked, mutexWoken, mutexStarving, mutexWaiterShift)
}
//output
1 2 4 3
- 按位清除(&^)
 
- 执行规则: a &^ b == a & (^b),即与上非b。
 - 语句用途: 把a中某些位置清空(置0),某些位置是指b中为1的位置。
一般是 待清空的数a &^ 标志位为1的等长数字b == 执行后,标志位被清空,其他数字不变。 
package main
import (
	"fmt"
)
func main() {
	// new &^= mutexWoken,就是清除mutexWoken位,置为 0
	fmt.Println(1 &^ 1)
	fmt.Println(0 &^ 1)
	fmt.Printf("%b\n",1 & ^1)
	fmt.Printf("%b\n",0 & ^1)
}
//output : 从执行结果看,
0
0
0
0
- 设置,清空和读取1个标志位
 
package main
import (
	"fmt"
)
// 1.3 与 1.7 老的实现共用的常量
const (
    mutexLocked = 1 << iota // mutex is locked
    mutexWoken
)
var state int32
func main() {
	fmt.Printf("%b,%b,%b\n",state,mutexLocked,mutexWoken)
	// 1. "|"写入标志位(置1)
	wokenState := state | mutexWoken
	wokenState |= mutexLocked
	fmt.Printf("%b,%08b\n",wokenState,wokenState) // %08b: 输出8位,位数不足填充0
	fmt.Printf("%b,%b\n",wokenState & mutexLocked,wokenState & mutexWoken) // 3.读取标志位. 如果标志位是1,读取结果就是mutexLocked或mutexWoken
	// 2. "&^"清空标志位,即置0
	wokenState &^= mutexWoken
	fmt.Printf("%08b\n",wokenState)
}
//output
0,1,10
11,00000011
1,10
00000001
raceenabled是什么代码
参考附录11.
raceenabled相关的代码全部忽略,这是golang内部使用thread-sanitizer用于扫描线程安全问题的诊断代码。
参考资料
1.图解CAS
2.GO中CAS代码示例
3.CAS乐观锁,最精确的文字描述.
4.CAS会导致"ABA问题"
5.Go-iota使用例子
6.sync.Mutex的实现和演进
7.Go语言中你所不知道的位操作用法 TODO: 通过位操作实现用户类型的存储,更新和修改。如[一个qq号可以用VIP会员,SVIP超级会员,蓝钻用户,黄钻用户,红钻用户....]
8.位清除(&^)有哪些应用?
9.go语言中获取变量类型的三种方法
10.Golang中的int类型和uint类型到底有多大? TODO: int和uint是根据 CPU 变化的!!!
11.毛剑-1.3sync.Mutex源码解析
Go中锁的那些姿势,估计你不知道
【协作式原创】查漏补缺之Golang中mutex源码实现(预备知识)的更多相关文章
- 【协作式原创】查漏补缺之Golang中mutex源码实现
		
概览最简单版的mutex(go1.3版本) 预备知识 主要结构体 type Mutex struct { state int32 // 指代mutex锁当前的状态 sema uint32 // 信号量 ...
 - 半夜思考之查漏补缺, 在 Spring中, 所有的 bean 都是 Spring 创建的吗 ?
		
Spring 是一个 bean 容器, 负责 bean 的创建, 那么所有的 bean对象都是 Spring 容器创建的吗 ? 答案是否定的. 但是乍一想, 好像所有的对象都是 Spring 容器负责 ...
 - 查漏补缺:Vector中去重
		
对于STL去重,可以使用<algorithm>中提供的unique()函数. unique()函数用于去除相邻元素中的重复元素(所以去重前需要对vector进行排序),只留下一个.返回去重 ...
 - Java查漏补缺(3)(面向对象相关)
		
Java查漏补缺(3) 继承·抽象类·接口·静态·权限 相关 this与super关键字 this的作用: 调用成员变量(可以用来区分局部变量和成员变量) 调用本类其他成员方法 调用构造方法(需要在方 ...
 - Java基础查漏补缺(2)
		
Java基础查漏补缺(2) apache和spring都提供了BeanUtils的深度拷贝工具包 +=具有隐形的强制转换 object类的equals()方法容易抛出空指针异常 String a=nu ...
 - CSS基础面试题,快来查漏补缺
		
本文大部分问题来源:50道CSS基础面试题(附答案),外加一些面经. 我对问题进行了分类整理,并给了自己的回答.大部分知识点都有专题链接(来源于本博客相关文章),用于自己前端CSS部分的查漏补缺.虽作 ...
 - Go语言知识查漏补缺|基本数据类型
		
前言 学习Go半年之后,我决定重新开始阅读<The Go Programing Language>,对书中涉及重点进行全面讲解,这是Go语言知识查漏补缺系列的文章第二篇,前一篇文章则对应书 ...
 - 《CSS权威指南》基础复习+查漏补缺
		
前几天被朋友问到几个CSS问题,讲道理么,接触CSS是从大一开始的,也算有3年半了,总是觉得自己对css算是熟悉的了.然而还是被几个问题弄的"一脸懵逼"... 然后又是刚入职新公司 ...
 - js基础查漏补缺(更新)
		
js基础查漏补缺: 1. NaN != NaN: 复制数组可以用slice: 数组的sort.reverse等方法都会改变自身: Map是一组键值对的结构,Set是key的集合: Array.Map. ...
 
随机推荐
- ServletContext的使用
			
ServletContext: ServletContext表示Servlet应用程序.每个Web应用程序只有一个上下文.在将一个应用程序同时部署到多个容器的分布式环境中,每台Java虚拟机上的Web ...
 - Codeforces Round #624 (Div. 3) F
			
题意: 给出n的质点,带着初位置和速度: 如果中途两点可以相遇dis(i,j)=0: 如果不可以相遇,mindis(i,j): 求n个点的两两质点最小dis(i,j)之和 思路: 因为当初位置x和速度 ...
 - maven中的pom.xml中的scope的作用
			
pom.xml配置文件中, <dependency>中的<scope>,它主要管理依赖的生效范围.目前<scope>可以使用5个值: * compile,缺省值,适 ...
 - Chrome - 使用 开发者工具 对页面截图
			
概述 使用 开发者工具 对页面截图 背景 经常需要截图 常用的截图模式有这些 窗口截图 区域截图 gif 问题 Chrome 如何截长图 firefox 好像有插件 1. 解决: 使用 Chrome ...
 - VIM - ex 命令行的窗口切换
			
1. 概述 操作 vim 同时编辑多个文件 约定 ctrl 使用按键 ctrl + w 时, 写作 ^W 思路 我记得这个由两个机制 ex 命令与缓冲区 分屏 2. 场景 文件 file1 file2 ...
 - Linux - XShell - alt 快捷键的设置
			
1. 概述 命令行的 alt 快捷键可能会冲突 2. 环境 os win10 centos7 xshell xhell6 3. 场景 开启 centos7 虚拟机 在 win10 打开 xshell6 ...
 - 作业2:go实现一个压测工具,具备upload功能
			
作业:go实现压测工具,具备upload功能. upload内容是.tar.gz,使用http协议上传. 要求:upload的文件名,http目的地址灵活可配. deadline:2019.4.30
 - T114048 [RC-02] yltx数对 (打表)
			
这题如果全部打表的话,文件大小会有65kb,超了,所以只打出一半,前一半用程序算就可以了,并不会超时. 如果算法优化的好,其实可以打的更少. #include <bits/stdc++.h> ...
 - RTT学习之软件包
			
网络工具集 (NetUtils) Ping 工具: 是一种网络诊断工具,用来测试数据包能否通过 IP 协议到达特定主机,依赖于LWIP,支持域名和IP访问: NTP 工具:NTP 是网络时间协议 (N ...
 - 【JavaScript基础#2】
			
" 目录 #. 函数 1. 定义 2. arguments 参数 3. 全局变量与局部变量 4. 语法分析 #. 内置对象和方法 1. 自定义对象 2. 类之继承 3. Date 4. JS ...