若是两个或者多个
goroutine
在没有相互同步状态的状况下同时访问某个资源,而且同时对这个资源进行读写的时候,对于这个资源就处于相互竞争状态(race candition)。下面来看一个相互竞争的例子。
var number int var wait sync.WaitGroup func main() { wait.Add(2) go updateNumber(20000)//加20000 go updateNumber(30000)//加30000 wait.Wait() fmt.Println(number) } func updateNumber(addNumber int) { for i:=0;i<addNumber ;i++ { number ++ } wait.Done() }
上面这个例子,咱们指望获得的值应该是500000
,可是咱们最后获得值,并非500000
,并且每次获得的结果是不同的。这是为何呢?由于在两个goroutine
中没有同步number
的当前值,就会存在两个goroutine
对number
值重复赋值的问题,形成值覆盖。这样就得不到咱们预期的结果。安全
上面的例子咱们能够看到,若是没有对竞争的资源进行有效的管理以及合理的处理,并发程序就会变的很复杂,而且会产生一些意想不到的错误。因此咱们须要对竞争资源进行管理来避免这些问题。
Go
中提供一些传统的方式来处理这类问题
原子函数可以以很底层的加锁机制来同步访问整型变量和指针,咱们可使用原子函数来处理竞争问题。
var number int32 var wait sync.WaitGroup func main() { wait.Add(2) go updateNumber(20000) go updateNumber(30000) wait.Wait() fmt.Println(number) } func updateNumber(addNumber int) { defer wait.Done() for i:=0;i<addNumber ;i++ { atomic.AddInt32(&number,1) } }
这里咱们使用了atmoic
包的AddInt32
函数。这个函数会同步整型值的加法,
方法是强制同一时刻只能有一个goroutine
运行并完成这个加法操做。当goroutine
试图去调用任
何原子函数时,这些goroutine
都会自动根据所引用的变量作同步处理。atmoic
包中还提供了Load
与Store
方法,对资源进行安全的读与写。并发
另外一种方式是建立一个互斥锁来锁住一个区域,来保证同一个资源不会被同时修改或者使用。保证当前只有一个
goroutine
在执行当前区域的代码。
var ( number int32 wait sync.WaitGroup mutex sync.Mutex ) func main() { wait.Add(2) go updateNumber(20000) go updateNumber(30000) wait.Wait() fmt.Println(number) } func updateNumber(addNumber int) { defer wait.Done() for i:=0;i<addNumber ;i++ { mutex.Lock() // 加锁 number++; mutex.Unlock() //释放锁 } }
上面的代码片断,在Number
改变的先后对当前区域加锁,最后也能获得咱们的目的,可是这样的会话,每次在number
变动的时候,都会建立锁与释放锁,会对性能产生很大的影响。其实咱们能够在for
循环区域来加锁。函数
func updateNumber(addNumber int) { defer wait.Done() mutex.Lock() // 加锁 for i:=0;i<addNumber ;i++ { number++; } mutex.Unlock() //释放锁 }
后面这种形式的效率明显是要比第一种高不少的。因此咱们程序有使用互斥锁的话,须要考虑加锁的粒度问题。性能
虽然上面上面两种方式也能够解决竞争问题,可是在go
中有一种更好的方式来解决这个问题,那就是goroutine
的好兄弟channel
。因为channel
的内容比较多,因此我将单独写一个笔记来记录这方面的问题。期待下一篇的更新