Golang基础--加锁与原子操作

发布时间 2023-05-06 23:15:59作者: 99号的格调

前言

  在实际项目开发中,有时会面临同一时刻将多个goroutine作用于同一个对象的情况,此时,他们之间会发生冲突,这种情况称为数据竞态问题。例如:

package main

import (
    "fmt"
    "time"
)

var count int

func main() {
    go CountPlus(10000)
    go CountPlus(10000)
    time.Sleep(time.Second)
    fmt.Println(count)
}
func CountPlus(times int) {
    for i := 0; i < times; i++ {
        count++
    }
}

  正常因该会返回20000,但是结果却不是。原因在于将两个goroutine作用于同一个count。

  解决方案:加锁

package main

import (
    "fmt"
    "sync"
    "time"
)

var count int
var lock sync.Mutex

func main() {
    go CountPlus(10000)
    go CountPlus(10000)
    time.Sleep(time.Second)
    fmt.Println(count)
}
func CountPlus(times int) {
    for i := 0; i < times; i++ {
        lock.Lock()
        count++
        lock.Unlock()
    }
}

  声明Mutex类型的变量lock,并通过lock调用Lock()函数进行加锁,待操作完成后,在调用UnLock()解锁,在加锁和解锁函数之间的代码将受到保护,在同一时间仅有一个goroutine在执行受保护的代码。

  上述提及的Mutex为互斥锁,能很好的避免多个goroutine同时操作同一数据。

RWMutex读写互斥锁

  当某个goroutine获得读操作后,其他尝试进行读操作的goroutine也能正常获得锁,但是需要进行写操作的gioroutine会继续等待;

  当某个goroutine获得写操作的锁后,由于数据很可能发生改变,因此接下来的无论是读操作还是写操作都会继续排队等待。

  示例代码:

package main

import (
    "fmt"
    "sync"
    "time"
)

var countTest int
var locker sync.RWMutex

func main() {
    for i := 1; i <= 3; i++ {
        go Write(i)
    }
    for i := 1; i <= 3; i++ {
        go Read(i)
    }
    time.Sleep(10 * time.Second)
    fmt.Println("countTest的值为:", countTest)
}
func Read(i int) {
    fmt.Println("读操作:", i)
    locker.RLock()
    fmt.Println(i, "读countTest的值为:", countTest)
    time.Sleep(1 * time.Second)
    locker.RUnlock()
}
func Write(i int) {
    fmt.Println("写操作", i)
    locker.Lock()
    countTest++
    fmt.Println(i, "写countTest的值为:", countTest)
    time.Sleep(1 * time.Second)
    locker.Unlock()

}

这里解释一下代码的运行逻辑:程序一开始,就执行了写操作,其他goroutine只能等待其完成。但此时,后续的goroutine都开始执行,只不过运行到有锁的地方暂停了,因此可以看到大量“读操作”和“写操作”同时输出

  等待一秒后,3次读操作不会对数据发生更改,因此几乎同时完成操作

  再等待一秒后,2次写操作排队进行,然后完成操作

  最终,count被累加3次,值为3