Go(Golang)_12_竞态
Go_12_竞态
- 竞态
- 互斥锁
- 读写锁
- 初始锁
- 上下文
- emptyCtx
- cancelCtx
- timerCtx
- valueCtx
- 常用函数
- 通道
- 无缓冲通道
- 缓冲通道
- 单向通道
- 多路复用
- 实现原理
竞态
竞态(Race Condition):多个goroutine在没同步时对共享资源进行读写操作
1)本质:多个goroutine在交错顺序执行时,程序无法确定共享资源正确结果;
2)对共享资源读写操作必须是原子化的(同一时刻仅能一个goroutine操作);
并发安全:对象在被多个goroutine调用时,没其他机制下仍能正常工作
1)并发安全类型:其所有可访问方法和字段皆是并发安全的;
2)包界别导出的对象通常认为是并发安全的;
//程序中拥有多个goroutine时,其执行顺序是无法确定的
共享资源实现并发安全的3种方式:
1)不修改共享资源(不切实际);
2)避免多个goroutine访问同一个共享资源(通道);
3)允许多个goroutine访问同一共享资源,但限定时间内仅一个访问(锁);
互斥锁
互斥锁(sync.Mutex):可对共享资源进行读写操作的锁(单读单写锁)
1)临界区:在上锁和释放锁区间可对共享资源进行读写操作;
2)可搭配defer关键词确保每次上锁后都会释放锁;
互斥锁对象的定义格式:var 对象名 sync.Mutex
1)上锁格式:对象名.Lock()
2)释放锁格式:对象名.Unlock()
如:通过互斥锁实现两个goroutine修改同一变量
1)编写程序;
2)运行结果;
读写锁
读写锁(sync.RWMutex):仅可对共享资源进行写操作的锁(多读单写锁)
1)外部函数获取锁后,其调用再次尝试获取锁会导致死锁(产生宕机);
2)允许多个读操作并发执行,但写操作仅能一个goroutine执行;
//常用于竞争激烈场景(普通场景下慢于读写锁)
读写锁对象的定义格式:var 对象名 sync.RWMutex
1)上写锁格式:对象名.Lock()
2)写锁释放格式:对象名.Unlock()
3)上读锁格式:对象名.Rlock()
4)读锁释放格式:对象名.RUnlock()
//上读锁后,禁止任何写操作(写锁同理,禁止任何读操作)
自旋模式:goroutine持续检测是否可获得锁
1)自旋模式下其他goroutine释放的锁会被自旋goroutine立即获得;
2)自旋模式可充分利用CPU,避免goroutine的切换;
3)饥饿模式下不允许进入自旋模式;
goroutine成为自旋模式的条件:
1)CPU核数大于1;
2)已自旋次数小于4;
3)GMP中的P的数量大于1;
4)goroutine调度机制中的可运行队列必须为空;
饥饿模式:goroutine长时间未得到运行
1)本质:goroutine两次阻塞之间大于1ms就标记为饥饿模式再阻塞;
2)被标记为饥饿模式的goroutine会被首先获得被释放的锁;
初始锁
初始锁(sync.Once):确保被调用函数仅执行一次
1)被调用的函数功能通常为对其他资源进行初始化;
2)基于读写锁实现;
初始锁对象的定义格式:var 对象名 sync.Once
1)调用函数格式:对象名.Do(函数名)
2)Do()方法判断函数是否被执行,未执行则执行该函数(反之pass);
如:通过初始锁在循环中仅执行一次函数
1)编写函数;
2)运行结果;
//可用于实现单例模式
上下文
上下文(Context):控制多级并发goroutine
1)Context包由1个接口,4种实现和6个函数组成;
2)Context接口原型如下(所有类型context均基于Context接口):
type Context interface {// 返回个deadline和是否已设置deadline的标识Deadline() (deadline time.Time, ok bool)// 被关闭时返回个关闭channel(未关闭时,返回nil)Done() <-chan struct{}// 描述context关闭的原因(未关闭时,返回nil)Err() error// 在树状分布的goroutine之间共享键值对(未有对应键,返回nil)Value(key interface{}) interface{}
}
emptyCtx
emptyCtx:共用全局变量
1)emptyCtx并未真正实现Context接口(仅是个整型别名);
2)emptyCtx相关原型如下:
type emptyCtx intfunc (*emptyCtx) Deadline() (deadline time.Time, ok bool) {return
}func (*emptyCtx) Done() <-chan struct{} {return nil
}func (*emptyCtx) Err() error {return nil
}func (*emptyCtx) Value(key interface{}) interface{} {return nil
}
Background()和TODO()函数可创建empty实例,其原型如下
1)创建实例
var (background = new(emptyCtx)todo = new(emptyCtx)
)2)Background()函数
func Background() Context {return background
}3)TODO()函数
func TODO() Context {return todo
}
cancelCtx
cancelCtx:基于Context上添加可安全关闭本身和子goroutine的功能
1)内部实现的cancel()方法可安全关闭本身和子goroutine;
2)cancelCtx的结构体原型如下:
type cancelCtx struct {Contextmu sync.Mutex // 互斥锁(保证关闭期间的线程安全)done atomic.Value // 获取关闭context的通知children map[canceler]struct{} // 记录子goroutineerr error // 关闭原因
}
使用WithCancel()函数须知:
1)cancelCtx实例的父节点必须也可被关闭类型;
2)若父节点不支持关闭,则继续向上查询直至找到支持可关闭的类型;
3)若均不支持被关闭则启动个goroutine做伪父节点,同时监控原父节点;
//启动的goroutine会在父节点结束时,通知cancelCtx实例执行cancel()方法
timerCtx
timerCtx:基于cancelCtx上添加达到存活/过期时间自动关闭的功能
1)timerCtx的结构体原型如下:
type timerCtx struct {cancelCtxtimer *time.Timer // 触发自动关闭的定时器deadline time.Time // 记录关闭的最终时间
}
使用WithTimeout()函数须知:
1)需指定过期时间;
2)本质:将过期时间转换为存活时间,并调用WithDeadline()函数;
valueCtx
valueCtx:在Context基础上添加多级goroutine共享键值对数据的功能
1)子节点查找对应键的数据时,会依次遍历所有父节点;
2)若所有父节点无对应键,则返回interface{};
3)valueCtx的结构体原型如下:
type valueCtx struct {Contextkey, val interface{} // 任意类型键值对
}
使用WithValue()函数须知:
1)指定的键值对包含创建的valueCtx实例(子节点);
2)创建的valueCtx实例无法关闭(其Done()方法无法返回);
//可给其指定支持关闭的父节点,在需关闭时关闭父节点和子节点
如:使用WithValue创建父子context,并传输数据和关闭
1)编写程序;
package mainimport ("context""fmt"
)type keytypeA stringtype keytypeC string //当键名相同时,防止查询本身func main() {var keyA keytypeA = "keyA"ctx, cancel := context.WithCancel(context.Background()) //使用empty实例做为父节点ctxA := context.WithValue(ctx, keyA, "ValA")var keyC keytypeC = "keyA"ctxC := context.WithValue(ctxA, keyC, "eggo")fmt.Println(ctxC.Value(keyA))fmt.Println(ctxC.Value(keyC))cancel() //调用WithCancel()放回的关闭方法
}
2)运行结果;
常用函数
(1)创建普通上下文
1)返回个empty实例
func Background() Context
//常用创建父级context1)返回个empty实例
func TODO() Context
//常用于不确定使用何种context时(静态工具可检测)
(2)创建各种实列的上下文
1) 将指定context包装成cancelCtx结构体的实例,并返回cancel方法
func WithCancel(parent Context) (ctx Context, cancel CancelFunc)2) 将指定context包装成timerCtx结构体的实例,并返回cancel方法
func WithDeadline(parent Context, deadline time.Time) (Context, CancelFunc)3) 将指定context包装成timerCtx结构体的实例,并返回cancel方法
func WithTimeout(parent Context, timeout time.Dureation) (Context, cancelFunc)
//逻辑上借用WithDeadline()函数实现4) 将指定context包装成valueCtx结构体的实例
func WithValue(parent Context, key, val interface{}) Context
通道
通道(Channel):Go语言中实现goroutine之间的通信机制
1)通道的零值是nil,且同种数据类型的通道是可比较的;
2)通道和goroutine之间的关系为:多对多;
3)通道分为:无缓冲通道、缓冲通道;
4)可通过通道实现管道;
通道的定义分为:无缓冲通道、缓冲通道
(1)无缓冲通道:通道名 := make(chan 数据类型)
(2)缓冲通道:通道名 := make(chan 数据类型,容量)
1)当通道仅用于同步时,数据类型可定义为“struct{}”或bool;
2)也可使用var关键词声明通道(值为nil);
通道的主要操作为:发送、接收
(1)向指定通道中发送数据:通道 <- 数据
1)数据的数据类型需与通道的数据类型保持一致;
(2)从指定通道中接收数据:变量1, 变量2 := <- 通道
1)变量的数据类型默认和从通道取出的数据类型一致;
2)若省略变量1和变量2,代表从通道中取出数据并丢弃;
3)变量2的数据类型为bool,为false时代表该通道已关闭且读完;
//变量2的名称一般被定义为“ok
”
使用通道需注意的3个事项:
(1)通过for range循环变量通过,格式:
for 接收数据变量 := range 通道 {程序段
}
1)当通道关闭后且已被读完,循环会自动结束;
2)其读写数据的阻塞机制与普通通道相同;
(2)通过通道实现信号量,格式:
定义信号量通道:通道名 := make(chan struct{},信号量数)
获取信号量:通道名 <- struct{} {}
释放信号量:<- 通道名
1)数据类型为struct{},因为其所占空间大小为0;
2)goroutine必须获取信号量才可继续执行,执行完需释放;
3)当信号量被抢占完时,剩余goroutine需等待信号量被释放再抢占;
(3)手动关闭指定通道格式:close(通道)
1)向关闭的通道发送数据,会发生宕机;
2)从关闭的通道接收数据,默认取出通道对应数据类型的零值;
3)若关闭通道时该通道还存在数据,则默认先取出通道中的剩余数据
//关闭通道的操作不是必须的(GC会根据该通道是否可访问对其自动回收)
无缓冲通道
无缓冲通道(Unbuffered Channel):通道中数据即发即收(可理解容量为1)
1)无缓冲通道会导致限制发送或接收的goroutine处于阻塞等待状态;
2)无缓冲通道可实现发送和接收的goroutine处于同一时间交换数据;
goroutine泄露:运行较缓慢的goroutine使用无缓冲通道导致数据丢失
1)GC不会自动回收泄露的goroutine(需手动回收)
如:2个goroutine通过无缓冲通道传递数据
缓冲通道
缓冲通道(Buffered Channel):在通道的基础上添加一个元素队列
1)向缓冲通道发送的数据:元素队列的末尾处添加;
2)从缓冲通道中接收数据:从元素队列的头部取出;
3)缓冲通道填满时:发送的goroutine会阻塞到该缓冲通道可接收数据时;
4)缓冲通道为空时:接收的goroutine会阻塞到该缓冲通道有数据可接收时;
//len(通道名)返回当前通道含有的元素个数,cap(通道名)返回通道的容量
如:2个goroutine通过缓冲通道传递数据
单向通道
单向通道:通道被当成函数参数时,可限定其操作范围
1)单向通道分为:仅发送单向通道、仅接收单向通道
2)双向通道可隐式转换为任意单向通道(反之不可);
单向通道的定义格式(需在函数定义中):
1)仅发送单向通道:func 函数名(参数 chan <- 数据类型){}
2)仅接收单向通道:func 函数名(参数 <- chan 数据类型){}
//双向通道的定义格式为:func 函数名(参数 chan 数据类型) {}
如:在函数中定义单向通道
1)编写程序;
2)运行结果;
多路复用
多路复用(select):通过指定通道的操作决定执行分支程序段
1)当同时多个分支满足情况时,编译器会随机选择个分支执行;
2)若通道的值为nil,则该分支永远都不会被执行;
//可使用无任何分支的select语句实现永久阻塞
多路复用的定义格式:
select {case 通道操作1:程序段case 通道操作N:程序段default:程序段
}
1)若匹配不到特定操作时,执行默认的default分支的程序段;
2)若省略default语句,则匹配不到操作时select语句会永久阻塞;
3)搭配for循环使用时,跳出循环需使用return语句(break只能跳出select);
如:通过多路复用实现偶数输出
1)编写程序;
2)运行结果;
//也可用于监控多个不同的通道
实现原理
runtime/chan.go中通道的数据结构定义:
type hchan struct {qcount uint // 环形队列中剩余的元素(数据)个数dataqsiz uint // 环形队列长度(可存储元素的个数)buf unsafe.Pointer // 指向环形队列的指针elemsize uint16 // 每个元素的大小closed uint32 // 是否关闭elemtype *_type // 元素(数据)类型sendx uint // 队列下标:指定元素写入时在队列中的位置recvx uint // 队列下标:指定下个本读取元素在队列中的位置recvq waitq // 等待读数据的协程队列sendq waitq // 等待写数据的协程队列lock mutex // 互斥锁
}
1)环形队列:内存中用于存储通道数据的缓冲区;
2)任何情况下recvq和sendq至少有一个为空(select语句除外);
3)创建通道的本质:初始化hchan结构体(内部调用make()函数实现);
关闭通道时会同时唤醒recvq和sendq队列中的G
1)recvq中的G会获取对应数据类型的零值;
2)sendq中的G会触发panic;
//关闭值为nil的通道也会触发panic
如:向通道写数据的流程
如:从通道读数据的流程
Go(Golang)_12_竞态相关推荐
- golang race 竞态检测
-race 属性用来做竞态监测,判断是否存在并发读写单个变量的情况.在执行 go test.go build.go run 的时候都可以执行这个参数. 这个检测是发生在运行时的,如果你的代码没有被执行 ...
- golang data race 竞态条件
golang race condition 竞态条件 data race race condition golang race detector golang的协程机制使得编写并发代码变得非常容易,但 ...
- eclipse运行go test_在 Go 中发现竞态条件 (Race Conditions)
当我意识到我一直在处理和解决的问题有一个专有名词描述的时候,我总会觉得这事十分有趣.这次出现这种情况的是竞争条件(Race Conditions).当你处理多个 routine 共享某类资源的时候,不 ...
- 竞态条件的赋值_《Java并发编程实战》读书笔记一:基础知识
一.线程安全性 一个对象是否是需要是线性安全的,取决于它是否需要被多个线程访问 当多个线程访问某个类时,不管运行时环境采用何种调度方式或者这些线程将如何交替执行,并且在主调代码中不需要额外的同步,这个 ...
- 线程学习5——竞态条件
竞态条件 概述:如果两个或两个以上的线程同时访问相同的对象,或者访问不同步的共享状态.就会出现竞态条件. 举例:如果多个线程同时访问类StateThread中的方法,最后结果会如何呢? 定义一个类St ...
- Linux系统时间和时序,什么是时序竞态 Linux系统时序竞态问题分析
什么是时序竞态?将同一个程序执行两次,正常情况下,前后两次执行得到的结果应该是一样的.但由于系统资源竞争的原因,前后两次执行的结果有可能得到不一样的结果,这个现象就是时序竞态.时序竞态前导例 在讲时序 ...
- 【Linux开发】linux设备驱动归纳总结(四):5.多处理器下的竞态和并发
linux设备驱动归纳总结(四):5.多处理器下的竞态和并发 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx ...
- Linux内核的并发与竞态、信号量、互斥锁、自旋锁
/************************************************************************************ *本文为个人学习记录,如有错 ...
- 关于JavaScript并发、竞态场景下的一些思考和解决方案
前言 时间是程序里最复杂的因素 编写 Web 应用的时候,一般来说,我们大多时候处理的都是同步的.线性的业务逻辑.但是正如开篇所说的"时间是程序里最复杂的因素",应用一旦复杂,往往 ...
最新文章
- vim 7.4同时支持python 2.x和3.x问题调研
- EFQRCode:自动生成花式二维码
- hdu4279 找规律+小想法
- android获取自定义属性,android 自定义控件中获取属性的三种方式(转)
- Python如何打包EXE可执行文件
- 高效率学习Java编程提升自我
- SAP成都研究院数字创新空间小伙伴们在2020 SAP上海DKOM
- genymotion 极速模拟器
- 360发布穿戴设备“儿童卫士”手环
- phoenix hbase java_java jdbc访问hbase phoenix
- quantaxis使用docker安装,解决了一个很奇特的问题
- Java进阶之网络编程
- webClient请求JAVA超时解决方案
- 计算机网络系统组播功能_全国计算机等级考试四级计算机网络考试大纲(最新版2018年版)...
- 设计模式的征途—7.适配器(Adapter)模式
- Atitit 前后端交互模式 目录 1.1. Ajax	1 1.2. Fetch api	1 1.3. 服务端脚本模式(简单快速)	1 1.4. 浏览器注入对象、函数	1 1.5. 浏览器插件模式
- ENVI 工具箱汉翻译汉化
- 华为路由器接口如何区分_华为新一代路由评测,自带NFC,一碰就能联网
- macos可以升级到指定版本吗_[macOS]如何升级更新 Mac 系统
- 算法学习之狄克斯特拉算法