[深度]Go同步和并发设计模式
在 2019年第五届 Gopher China 大会上,新浪微博平台研发中心架构组的工程师晁岳攀进行了主题为《Go同步和并发设计模式》的演讲,内容分为5个主题:
基本的同步原语
拓展同步原语
原子操作
Channel
内存模型
以下为演讲实录。
01
基本同步原语
互斥锁有两个状态,有正常状态,还有饥饿状态.
正常状态是等待Goroutine按照FIFO顺序等待.唤醒的Goroutine不会直接拥有锁,而会和新请求的锁Goroutine竞争,谁拥有谁就获取这个锁.如果一个等待的Goroutine超过1毫秒的情况下,Mutex会转成饥饿状态,会在第三位标记为1,说明这个锁是进入饥饿状态.
在饥饿状态下,当一个持有的Unlock的Goroutine直接交给等待队列中的第一个.饥饿的Goroutine处理结束之后,发觉后面没有等待的Goroutine了,或者等待的Goroutine时间小于1毫秒,又转入正常状态.这样避免饥饿的状态.
TryLock,Count
Mutex IsWoken,IsStarving
上面这个例子看起来乱七八糟,但是我可以说你可以比喻奥运会比赛的时候,十个运动员站在起跑线正在做热身运动,然后有个裁判指发令枪,看大家准备好了没有,如果准备好了发令枪一响,所有运动员开始起跑.
我喜欢跟JAVA比较,JAVA有两个数据结构,他有点类似.下面一个数据也类似,我们一开始定义了一个Mutex这一个Lock的接口具体的实践,相当于十个运动员准备好了,可以继续往下进行了,在这个过程当中,启动十个Goroutine,Goroutine开始的时候会调用MLock,做一些准备,或者初始化,准备好以后,就释放信号至少我这个Goroutine准备好了,下面判断这个条件是不是真的准备好了,里面调用一个CWait等待这个条件,如果没有准备好会等在这儿,等待信号的发生,如果信号被触发以后,条件准备好了,大家可以一起做下一步的动作.前面有两个假动作,之所以是假的,因为在条件没有准备好的情况下,你可以调用这个方法,但是你这个方法只会给前面的Goroutine一个假信号.等都设置好了以后,发一个准备好的信号,下面Goroutine可以继续执行.
Cond最主要三个方面,一是Broadcast,我给所有的Goroutine信号,Signal给所有的Goroutine信号,Wait是等待条件是否满足.前面两个不要求加锁,调用Wait的时候一定要加锁.而且里面有一个先解锁后再加锁,中间有时间间隔,如果你对其他变量进行更改,可能有数据不一致的状态,所以前面用FOR判断条件是否真的满足了.
Waitgroup
另外很常用是Waitgroup,它更像Java的Countdown、Lach的结构,CyclicBarrier),比如说百米赛跑可能20秒就解决完了,就统计结果,我会用Barrier统计运动员是否完成了,统一做下一步的事情.这两个数据过程中可以支一个Waitgroup实现.Waitgroup有一个ADD方法,一开始是多少,可以一次设置好,也可以中间多次进行设置.这个ADD方法可以设置负值,相当于减相应的数目,如果计数期小于零会Panic.所以避免.前面如果不为零,所有调用Wait方法会被阻塞住,等待所有的Waitgroup进行下一步工作.
Waitgroup可以重用,但是重用的时候要避免下面几种情况.
ADD的时候一定要在Wait方法之前完成.
第一个Waitgroup方法之前要把Add增加上,后面错误的例子是说,因为Waitgroup和下面Wait是并行执行,有可能add方法没有执行,Wait方法已经执行了,前面的Goroutine没有执行,后面Waitgroup已经执行过了.
另外,虽然说Waitgroup是可以重用的,但是避免在Done分子,Done是add的附值,这个是有一个并行.我把错误的贴在这儿,因为Waitgroup检测很多状态不一致的情况,一旦一个Wait没有执行完,前面的Done已经执行结束,接着再add,状态就不知所措了,到时候看到一个错误的状态.这种情况是说,等于Wait可以执行多次,但是Done一定避免超过设置的数,前面设置10,后面Wait调用几次没有关系,但是右边的例子我设置是10,里面执行10次,如果外面5,如果我执行多一次,Done会导致Panic.这是一个Wait、Done还有Add执行当中避免的错误.
所以我们在Waitgroup使用的时候,保持这样一个原理,你前面一开始要执行Wait接受器设置好,然后等Wait,等Wait执行之后再重用,再进行新的Add方法增加,这样至少保证避免Add并发错误.
Once
once 的Do只执行一次.
避免死锁.
即使func panic,once 也认为他完成了.
once是执行单锁,可以执行一次初始化.单例有多种方法,一个是常量,再就是变量,可以用初始化操作,另外可以使用Getlnstance里面加一个锁,每一次获取一个变量有一个锁请求,这个会导致性能问题.通过Sync.once实现一次性初始化,性能不会带来特别的牺牲.once实现了一个done的功能,一开始检查Done完了,就返回了,这样性能不会带来太多牺牲.
所有性能框架,Go下面你会看到这样一句话,第一次使用不能被Copy,因为一开始我们所有的零值都是无锁的,使用后有状态的.原来有机会释放锁,但是Copy过来没有机会释放锁.
临时对象池 POOL
POOL临时对象池是暂时的,而不是长久的,因为可能在任何时候任意的对象可能被移除.但是可以安全并发访问,本身有一个装箱和开箱的操作.所以使用POOL一定避免有一些情况导致内存泄露的问题.这是Go标准库里面的Bug,本身的例子就是内存泄露了.
这个是一个BufPOOL池的例子.
这下面是我们标准包里面的两个错误的使用的例子,一个是FMT在格式化输出的时候有一个池,一开始放又回过来BufPOOL真相,放在这个池里.但是现在改与小于一定数量的BufPOOL才放到池子里,否则就丢掉了.在新的版本中会算已经修复的新版本中.
MAP
另外有一个MAP,我们知道Java的MAP不是安全,大家踩过这个坑.为了提供并发的访问,Go的MAP适用两个Cases,第一是设置一次多次读.另外如果多个Goroutine并发读写,更新不同的Key也是适用于这个MAP.这个MAP是内部实现了两个方面,用时间换空间的思想,使用两个MAP,MAP其中一个假如说这是一个MAP,他这里叫做带锁的功能,内部叫Derta,如果你增加新的数据放在DertaMAP,DertaMAP一直往这儿放,因为有锁所以性能不好,如果访问不好每次访问都是访问只读的MAP,就要开一个Mees转到Derta查询,查询一定次数,会把DertaMAP给只读MAP,Dertamap是空值.再增加新的MAP,Derta可以放在新的MAP里面.DertaMAP里面总是最新的数据.Range有可能新增加的不存在,现在整个MAP获取,如果没有再进行进一步加锁到Derta里面去.Range性能不好.
02
扩展同步原语
前面说了Mutex不能重入.但是我们编写的时候可能会有重入的方法,写的东西太多了没有办法解决这个问题.可以用标记,导致这个锁是谁持有,另外他已经重入多少次,通过两个变量的情况下,我们就可以来判断获取这个锁我们到底是请Goroutine是否持有这个锁,如果持有就加一个锁,如果不持有就让他等待.获取GoID有两个方法,一个是通过自己的方法解析,第二是通过Go获取ID.第二个方法性能更好.
ReentrantLock
ReentrantLock
我们学过操作系统都知道,他有两个概念,一个是二进制实现Mutex的功能,另外可以通过技术器的方式实现多个情况.
GoSemaphore可以初始化Semaphore量是多少,用这个量获取,释放这个信号量,信号量可以是一组资源,我有十台打印机,把这个信号量设置成十.等第十一就没有了,就等待,前面如果有等待的Goroutine会重新化解.
SingleFlight
另外找一个单飞模式,单飞模式是这样,有一组任务过来的时候,我可以挑出其中一个让他执行任务,等他执行结束把结果告诉等待剩下一系列的人让他返回,我选择一个执行,有好处是避免使用的状态.
ErrGroup
ErrGroup刚才有老师讲了,我说两个问题,Wait方法是等待所有的并发函数执行结束才能返回.即使其中的调用方法有错误之后,他也不会立马返回,必须等到所有的函数一起返回.通过方法返回的Error只保持第一个Error,其他后续请求函数也是错误的话,也没有办法找到你,除非用额外的变量退出.如果不是等待所有的函数返回,可以用Context,因为Context是内部机制,等所有完成之后,Context可以执行完,可以监测ErrorContext检测,到底是第一个错误请求,还是说函数都执行完了.
Spinlock
另外Spinlock用自旋锁,性能非常高.在压力不是特别大的情况下性能非常好.另外可以保护多个性能进行加锁,这个进程有共享的对象在操作系统上,可以通过稳健锁进行锁控制.
Concurrent map
Concurrent map和Java非常相似的,一个是访问Key,另外不断增加性能建设情况下,我可 以把这个锁进行细化,可以产生32个锁,然后分成32个Shared,这样可以快速进行锁的读取.
原子操作
原子操作及数据类型
另外atomic提供了很多类似的方法,我把这些函数、数据内容和方法进行分类,这样通过两页PPT把所有方法都记住了.一个是所有执行的类型是32、64、还有Uint32、Uint64,还有无指征.方法是提供Add方法,另外提供CAS的方法.如果读取就是Load,存储用Store,Swap和原来交换,返回原来的指征.
atomic 数据类型
int32
int64
uint32
uint64
uintptr
unsafe.Pointer
atomic 方法
AddXXX (整数类)
CompareAndSwapXXX(cas)
LoadXXX(读取)
StoreXXX (存储)
SwapXXX (交换)
Subtract方法
有Add没有Substract方法?
有符号类型 可以使用ADD负数
无符号类型 AddUint32(&x, ^uint32(c-1)),AddUint64(&x, ^uint64(c-1))
无符号类型减1 AddUint32(&x, ^uint32(0))???? AddUint64(&x, ^uint64(0)
value 方法
03
channel
channel 功能
Channel主要是用于信号通知,数据的交流,利用Channel可以实现锁.
channel特殊情况
Channel一些特殊的类型,这个表大家在用的时候一定要记住,如果记不住中间使用Channel的时候经常会犯一些错误.我把比较重要的点标出来,在某些行为会被Blog住,在某些情况会Panic.
Locker
这是利用Locker锁的结构.在后面我们Go数据模型会介绍,为什么使用Channel的方式,怎么实现锁的功能,主要是有一个规则在里面.
这是另外利用Channel,这里利用Channel有两种写法.
channel vs mutex
刚才说Channel实现锁,Mutex也可以实现锁,到底我们使用Channel还是使用Mutex.从G因为Channel因为Go有一篇文章说还是根据场景选择不同的言语.
1. channel 适用场景
Channel用在什么情况下呢?
传递的数据的时候,类似Rast把数据传递给你就不管了,以后传递给下一个Goroutine处理.
分发任务单元,做一个应用池的时候,前面把任务包装好,仍给其中某一个Worker就行了,Worker用Channel实现了任务列表,他从Channel取任务就OK.
另外交流异步的结果,可以利用Channel进行传递
还可以进行复杂编排功能.简单可以用RerderGroup就行了.
2. Mutex 适用场景
Mutex是对Cache和带状态的对象进行加锁,主要是对临界区的保护.
channel 使用的错误率
上个月在一次大会上有一个留美中国人分析了当前比较流行的Go框架的一些并发的错误,做了一个总结.这个文章应该是几个月之前已经登在网上了,上个月大会的时候正式宣讲.大家可以找找看,网上都有这个文章,很有意思的例子.流行的Go处理他们并发的错误非常多,包括标准库里面都有一些错误的使用,使用Channel和比使用标准的传统的基本同步言语错误率还要高,因为Channel刚才列出表有很多异常情况,这些异常情况,万一没注意,或者根本没有处理,会导致你会有意外的Panic的情况发生.
利用channe来实现一些设计模式
利用Channel可以实践一些Done的设计模式,可以用二分法递归的方式,或者用Fan—in的方式.另外使用Channel可以实现多个数据源取一个数据源,从一个数据源出来.同样还有善后的功能,多个数据源选一组输出.另外可以写Pipeline的功能.写软件具体不讲了,我只是给大家提供这样一个Channel的设计模式.另外还有写流动功能,比如说Reduce的功能.
orDone
or
or 二分法递归
or 反射
fan-in递归
fan-in 反射
fan-out
tee
pipeline
stream-skip
stream-take
stream-map
stream-reduce
04
内存模型
Go内存模型所提供的就是Go内存模型的文章,内存模型描述了线程通过内存的交互以及数据的共享使用.
历史
最早是Java,C++也定义了内存模型.Go。
go 内存模型
在文章中定义了内存模型是这样定义的
对一同一个变量如何保证在一个Goroutine对此变量读的时候能观察到其他Goroutine对此变量的写.
修改一个同时被多个goroutine并行访问的变量时,需要进行串行化访问
通过channel或者其他同步原语进行串行化访问
happen-before
同一个 Goroutine
同一个Goroutine他的执行是和代码定义的顺序是一样的.这个里面会有一些重复的问题,但是不影响你的理解,你可以认为是和你编写的顺序是按照编写的顺序执行的.
strict partial order
init函数
对于Init函数,如果在PackageP引入了定义的PackageQ,就是这个Q的init函数一定要在Happens beforeP的Init之前执行,Main函数一定在其他引用函数之后才执行.
go语句
Go运行其中一个函数,在Go的语句之前定义的变量,一定是对Goroutine函数是可见的,里面如果打印前面的变量一定能正面打印出来.不会打印空的,还没赋值的状态
channel
mutex/rwmutex
Mutex和RWMutex保证你在设计你的代码的时候,你如果不确定你代码的执行顺序是什么样,可以对照规则排查你的代码是不是写的按照你所设想的顺序执行.
waitgroup
once
once.do 方法的返回一定happen before 任何一个方法 once.do 的返回
atomic Atomic
没有官方的保证,建议不要依赖atomic保证内存的顺序.
重磅活动预告
Gopher Meetup 北京站即将开启。来自探探、美团、阿里巴巴、蚂蚁金服的大咖讲师讲带来 Go 开发领域的一线实践经验分享,尽在11月30日,IFC国际财源中心!
报名请戳:阅读原文
Go中国
扫码关注
国内最具规模和生命力的 Go 开发者社区
欢迎投稿,请联系:
situzhihui@163.com
[深度]Go同步和并发设计模式相关推荐
- 《Go 同步和并发设计模式》培训结束,get满满干货!
5月26日,在北京举办了GoCN和滴滴再次携手邀请了来自微博研发平台架构中心资深架构师-晁岳攀老师在北京举办的<深入Go 并发编程>培训专场,本次参加培训报名人数达到170余人,远超我们一 ...
- 《Go 同步和并发设计模式》培训会后整理
今天参加<Go 同步和并发设计模式>主题培训,虽然早上下雨,但是大家一般还是都在9点左右赶到现场.本次分享时间很充沛,晁老师讲的也很细致,4点就结束了高于预期啊呵呵,全程听下讲座来收获还是 ...
- Linux RCU机制详解[转]
一:前言 RCU机制出现的比较早,只是在linux kernel中一直到2.5版本的时候才被采用.关于RCU机制,这里就不做过多的介绍了,网上有很多有关RCU介绍和使用的文档.请自行查阅.本文主要是从 ...
- java设计模式并发_[高并发Java 七] 并发设计模式
[高并发Java 七] 并发设计模式 [高并发Java 七] 并发设计模式 为什么80%的码农都做不了架构师?>>> 在软件工程中,设计模式(design pattern)是对软件设 ...
- svn在linux下的使用(svn命令)[转]
svn在linux下的使用(svn命令)[转] 原地址:http://www.rjgc.net/control/content/content.php?nid=4418 1.将文件checkout到本 ...
- bigru参数计算_[数据挖掘]华中科技大学 李黎 周达明:基于CNN-BiGRU模型的操作票自动化校验方法...
原标题:[数据挖掘]华中科技大学 李黎 周达明:基于CNN-BiGRU模型的操作票自动化校验方法 智能变电站操作票校验是保障站内操作准确无误的重要环节,当前基于经验的人工校验方法主观性强,校验效率较低 ...
- 网络产品用户体验优化系列[一]概要
网络产品用户体验优化系列[一]概要 很高兴能在这里和大家专门沟通用户体验方面的话题,特别是能够收集大家对网络产品用户体验的反馈,以及网络产品团队在用户体验方面的优化和更新.这个系列我们只谈用户体验. ...
- 扬州十日记 [明]王秀楚
扬州十日记 [明]王秀楚 1645年清顺治2年己酉夏4月14日,督镇(官名)史可法从白洋河失守,仓皇退却到扬州,随即紧闭城门,死守扬州城. 满洲军随后而至,四月24日开始用大炮攻城 ...
- [翻译]Why Functional Programming Matters
Why Functional Programming Matters 函数式程序设计为什么至关重要 作者: John Hughes 翻译: CloudiDust [http://blog.csdn.n ...
最新文章
- 自然语言处理之机器处理流程
- Microsoft Message Analyzer (微软消息分析器,“网络抓包工具 - Network Monitor”的替代品)官方正式版现已发布...
- tf.layers.Dense与 tf.layers.dense的区别
- 七种布局显示方式效果及实现
- SAP 那点事BW HANA
- python 查看数据结构类型_python 数据结构类型总结(示例代码)
- java jdk1.8 jvm_JVM——Java内存模型 (JDK1.8)
- 外虚内实是什么意思_取名|为什么00后那么多梓涵?
- 黑马python培训靠谱吗-黑马程序员的Python怎么样?
- Linux daemontools安装及使用
- Win10喇叭图标出现红叉,没有声音,并且提示“未安装任何音频输出设备“的解决办法
- 流媒体后视镜方案关键技术--调节后视图像显示范围
- Android的三种绑定方式
- 3种常用的缓存读写策略
- Company interview process
- 』 [大话IT]我编的计算机基础教材,大家批判批判
- 《位置大数据隐私管理》—— 导读
- [AS日记]MacOS的Android Studio卡在Building Gradle Project info走不动 的处理方法
- stream流 lambda 练习
- 魔百盒M301A免拆机线刷固件-九联代工-S905L2(附教程)
热门文章
- 危机下毕业生求职的16条忠告
- 写给金融危机下年轻人的16条忠告(zt)
- Content-Type为“multipart/form-data“是什么意思?
- Keychron 键盘指南
- Matlab中pwelch的用法总结
- 计算机四级手机破解,计算机四级题库
- C++-009-setw、setfill操作符与left,right
- 爱奇艺加入龙蜥社区,携手打造多元化视频生态底座
- 坚鹏:广发银行江门分行金融科技与银行转型培训圆满结束
- 局域网中Openstack的VNC安全配置