go学习笔记-goroutine竞争状态

如果两个或者多个 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

上面这个例子,我们期望得到的值应该是500000,但是我们最后得到值,并不是500000,而且每次得到的结果是不一样的。这是为什么呢?因为在两个goroutine中没有同步number的当前值,就会存在两个goroutinenumber值重复赋值的问题,造成值覆盖。这样就得不到我们预期的结果。

上面的例子我们可以看到,如果没有对竞争的资源进行有效的管理以及合理的处理,并发程序就会变的很复杂,并且会产生一些意想不到的错误。所以我们需要对竞争资源进行管理来避免这些问题。 Go中提供一些传统的方式来处理这类问题
原子函数 atomic
原子函数能够以很底层的加锁机制来同步访问整型变量和指针,我们可以使用原子函数来处理竞争问题。
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

这里我们使用了atmoic包的AddInt32 函数。这个函数会同步整型值的加法,
方法是强制同一时刻只能有一个goroutine 运行并完成这个加法操作。当goroutine试图去调用任
何原子函数时,这些goroutine 都会自动根据所引用的变量做同步处理。atmoic包中还提供了LoadStore方法,对资源进行安全的读与写。

互斥锁 mutex
另一种方式是创建一个互斥锁来锁住一个区域,来保证同一个资源不会被同时修改或者使用。保证当前只有一个 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

上面的代码片段,在Number改变的前后对当前区域加锁,最后也能得到我们的目的,但是这样的会话,每次在number变更的时候,都会创建锁与释放锁,会对性能产生很大的影响。其实我们可以在for循环区域来加锁。

func updateNumber(addNumber int)  {
    defer wait.Done()
    mutex.Lock() // 加锁
    for i:=0;i

后面这种形式的效率明显是要比第一种高很多的。所以我们程序有使用互斥锁的话,需要考虑加锁的粒度问题。

虽然上面上面两种方式也可以解决竞争问题,但是在 go中有一种更好的方式来解决这个问题,那就是 goroutine的好兄弟 channel。由于 channel的内容比较多,所以我将单独写一个笔记来记录这方面的问题。期待下一篇的更新

期待一起交流

file

你可能感兴趣的:(golang,goroutine,锁,并发)