## 竞争条件
当我们能够没有办法自信地确认一个事件是在另一个事件的前面或者后面发生的话,就说明x和y这两个事件是并发的。
## 并发安全
一个函数在线性程序中可以正确地工作。如果在**并发**的情况下,这个函数依然可以**正确地工作**的话,那么我们就说这个函数是并发安全的,并发安全的函数不需要额外的同步工作。我们可以把这个概念概括为一个特定类型的一些方法和操作函数,对于某个类型来说,如果其所有可访问的**方法和操作都是并发安全**的话,那么类型便是**并发安全的。**
## 哪些是并发不安全的
导出包级别的函数一般情况下都是并发安全的。由于**package级的变量没法被限制在单一的gorouine**,所以修改这些变量“必须”使用互斥条件。
也就是说,包级别的变量是并发不安全的,包括所有普通类型和slice,map,数组.
## 竞争条件
**竞争条件指的是程序在多个goroutine交叉执行操作时,没有给出正确的结果**。竞争条件是很恶劣的一种场景,因为这种问题会一直潜伏在你的程序里,然后在非常少见的时候蹦出来,或许只是会在很大的负载时才会发生,又或许是会在使用了某一个编译器、某一种平台或者某一种架构的时候才会出现。这些使得**竞争条件带来的问题非常难以复现而且难以分析诊断**。
## 产生竞争条件的原因
所有对数据进行的操作,都会先拿到这个数据本身的值,然后再本身的值上面进行操作. 非并发情况下,肯定是顺序执行的.但是在并发条件下A先读S的值,此时B在A的后面也读到S的值了,然后B在S上+100,A也在S上+100.然后S的值就是100.因为B对S的操作丢失了.
```
S := 0
A读 //S = 0
B读 //S = 0
B写: S += 100 //S = 100
A写: S += 100 //S = 100
S最后就等于100了,因为A和B都不是在对方已经+100的基础上进行+100的.它们读到的都是0.
```
## 数据竞争(非常重要的概念)
无论任何时候,只要有**两个或以上goroutine并发访问同一变量**,且至少其中的一个是**写操作**的时候就会发生数据竞争。
## 一定要避免数据竞争
**一个好的经验法则是根本就没有什么所谓的良性数据竞争(不要心存幻想)**。
## 避免数据竞争三种方式
### 1.不去写变量
对于共享数据,所有的并发只读它,而不去修改它.(但是很多业务就是要写啊,所以这个方式不好)
### 2.避免多个goroutine访问变量
只能使用一个channel来发送给指定的goroutine请求来查询更新变量。这也就是Go的口头禅“不要使用共享数据来通信;使用通信来共享数据”。一个提供对一个指定的变量通过channel来请求的goroutine叫做这个变量的监控(monitor)goroutine。
下面的例子可以看的很清除,对于共享数据,专门使用一个goroutine来监控它数据操作的情况,所有的操作都是有顺序的.
**这里的秘密就在于,读是可以随便读,而写则通过channel来进行线性化.这样就避免了数据竞争**.
~~~
package bank
var deposits = make(chan int) // send amount to deposit
var balances = make(chan int) // receive balance
func Deposit(amount int) { deposits <- amount }
func Balance() int { return <-balances }
func teller() {
var balance int // balance is confined to teller goroutine
for {
select {
case amount := <-deposits:
balance += amount
case balances <- balance:
}
}
}
func init() {
go teller() // start the monitor goroutine
}
~~~
即使**当一个变量无法在其整个生命周期内被绑定到一个独立的goroutine**,绑定依然是并发问题的一个解决方案。例如在一条流水线上的goroutine之间共享变量是很普遍的行为,在这两者间会通过channel来传输地址信息。如果流水线的每一个阶段都能够避免在将变量传送到下一阶段后再去访问它,那么对这个变量的所有访问就是线性的。其效果是变量会被绑定到流水线的一个阶段,传送完之后被绑定到下一个,以此类推。这种规则有时被称为串行绑定。
怎么理解上面的话呢?就是可能一个变量是在函数中的,在函数内部使用了并发程序去操作它,那么依然是可以通过channel来将共享的变量绑定在一个channel上.那么这样也是线性的.只要是在往这个channel发送该变量之前没有再去使用它.
### 3.互斥
允许很多goroutine去访问变量,但是在同一个时刻最多只有一个goroutine在访问。这种方式被称为“互斥”.
- 基本语法
- 申明变量
- 常量
- 数据类型
- 强制类型转换
- 获取命令行参数
- 指针
- 概述
- new函数
- 函数
- 概述
- 不定参数类型
- 有返回值
- 函数类型
- 回调函数
- 匿名函数和闭包
- 延迟调用defer
- 工程管理
- 工作区
- src,pkg和bin目录
- 复合类型
- 概述
- 数组
- 概述
- 声明并初始化
- 拷贝传值
- slice
- 概述
- 创建切片
- 切片截取
- 切片和底层数组的关系
- slice常用方法
- 切片做函数参数
- map
- 概述
- map操作
- 结构体
- 概述
- 结构体初始化
- 结构体比较
- 结构体作为函数参数
- 结构体前加&
- 面向对象
- 概述
- 匿名组合
- 方法
- 值语义和引用语义
- 方法集
- 方法的继承
- 方法重写
- 方法值
- 接口
- 接口定义和实现
- 多态的表现
- 接口继承
- 接口转换
- 空接口
- 类型断言
- 异常处理
- error接口
- panic
- recover
- 文本文件处理
- 字符串操作
- 正则表达式
- json处理
- 文件操作
- 标准设备文件操作
- 并发编程
- 概述
- 并发和并行
- go语言并发优势
- goroutine
- goroutine概述
- 创建goroutine
- 主协程先退出
- runtime包
- Gosched
- Goexit
- GOMAXPROCE
- channel
- 多资源竞争
- channel类型
- 无缓冲channel
- 有缓冲channel
- 关闭channel
- 单向channel
- 单向channel特性
- 定时器
- Timer
- Ticker
- select
- select作用
- 超时
- sync
- 竞争状态
- 网络编程
- 网络概述
- 网络协议
- 分层模型
- 网络分层架构
- 层与协议
- 每层协议的功能
- 链路层
- 网络层
- 传输层
- 应用层
- socket编程
- 组合和继承
- 注意事项
- 细节
- go语言实现队列
- google工程师golang
- 基础语法
- 内建容器
- 面向"对象"
- 依赖管理
- 面向接口
- 函数式编程
- 错误处理和资源管理
- 测试与性能调优
- goroutine
- channel
- golang问题集
- 断言和类型转换
- Go语言圣经
- 入门
- 程序结构
- 命名
- 声明
- 变量
- 赋值
- 类型
- 包和文件
- 作用域
- 基础数据类型
- 整数
- 浮点数
- 复数
- 布尔型
- 字符串
- 常量
- 复合数据类型
- 数组
- slice
- map
- 结构体
- json
- 文本和HTML模板
- 函数
- 函数声明
- 错误
- 函数值
- 匿名函数
- defer
- panic
- recover
- 方法
- 方法声明
- 指针对象的方法
- 封装
- 接口
- 说明
- 接口是合约
- 实现接口的条件
- 接口值
- 类型断言
- 通过类型断言询问行为
- 类型开关
- Goroutines和Channels
- 协程
- channels
- 无缓冲channel
- 串联的channel
- 有缓冲channel
- 并发的循环
- select多路复用
- 并发的退出
- 并发问题的自我思考
- 基于共享变量的并发
- 竞争条件
- 互斥锁
- 读写锁
- 内存同步
- sync.Once
- 协程和线程
- 包和工具
- 测试
- 反射
- 什么是反射
- 为什么需要反射
- reflect.Type和reflect.Value
- 通过reflect.Value修改值
- 底层编程
