并发常见问题
竞争条件
百度定义:
竞争条件指多个线程或者进程在读写一个共享数据时结果依赖于它们执行的相对时间的情形。
竞争条件发生在当多个进程或者线程在读写数据时,其最终的的结果依赖于多个进程的指令执行顺序。
多个进程并发访问和操作同一数据且执行结果与访问的特定顺序有关,称为竞争条件。简单来说:
当两个或者多个操作必须按照正确的顺序执行,而程序并没有保证这个顺序,这就出现了竞争条件;通常会出现在一个并发操作读,另一个进行并发写入;代码示例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17package main
import (
"fmt"
)
func main() {
var number int
go func() {
number++
}()
if number == 0 {
fmt.Printf("number = %v", number)
}
}执行结果可能性就存在三种情况:
情况一: 不输出任何东西,因为 if判断在number赋值之后进行执行;
情况二: number = 1,if判断在number之前执行,fmt输出在number之后执行;
情况三: number = 0,if判断和fmt输出都在number被赋值之前执行;解决方案:
方案一: 在调试环境中使用,time睡眠,但是不是解决办法的有效方案;
方案二: 对变量操作进行原子操作,使用sync中的工具,比如互斥锁 读写锁等等;原子性
定义:
具备在一定的环境下,即运行的上下文中,具有不可分割和不可中断的性质; 在这样的条件下,我们可以认为这个东西是原子的;
概念:
上下文(context):
一些操作在一个上下文中是原子的但是在另一个 上下文中就不一定是原子性操作;原子性会根据当前定义的范围的改变而发生改变;
不可分割和不可中断:
就是说在定义的上下文中,原子的东西将会被完整的运行,并且在这种情况下不会同时发生任何事情,也就是在原子内部是同步进行;
原子举例说明:
1
2
3
4
5
6
7
8
9
10
11
12
13var number int
go func() {number++}()
number ++
可以拆分:
1. 检索 number 值
2. 对 number 增加
3. 存储 number 值
其中3步都是原子操作,但是将其合在一起就不一定是原子操作,是否具备原子性,要看该操作所在的上下文环境中.
如果该操作的上下文是: 无并发进程程序 则 number++ 是原子性
如果该操作的上下文是: goroutine,并且number并没有给其他的goroutine中使用,则 也是原子性;综上所述:
在考虑原子性时,优先要确定的是就是定义上下文或者范围,然后再考虑这些操作是不是原子性,一切都应该遵守该原则; 大部分的操作都不是原子性,比如函数, 方法以及程序等.
内存访问同步
对同一个变量的访问或修改,在访问内存之前,进行获取锁操作,对变量进行业务处理完毕后,对该内存所持有的锁进行释放,以便于其他线程进行资源的访问与修改.
使用sync中的互斥锁或读写锁,可以实现了对该内存访问同步执行,保证数据安全性;弊端就是对程序的性能产生很大的影响, 并带来一系列锁的问题(死锁), 锁定的临界区域大小确定, 以及临界区是否会频繁的进入与退出等问题;
死锁 活锁 饥饿