Golang基础--并发控制
前言
在实际项目开发中,有时会面临同一时刻将多个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
WaitGroup
WaitGroup,等待一组goroutine结束.
type WaitGroup struct {
noCopy noCopy
// 64-bit value: high 32 bits are counter, low 32 bits are waiter count.
// 64-bit atomic operations require 64-bit alignment, but 32-bit
// compilers only guarantee that 64-bit fields are 32-bit aligned.
// For this reason on 32 bit architectures we need to check in state()
// if state1 is aligned or not, and dynamically "swap" the field order if
// needed.
state1 uint64
state2 uint32
}
waitgroup对外提供三个借口,分别是
Add():
Wait()
Done()