有时候在Go语言代码中可能会存在多个goroutine同时操作一个资源的情况,这种时候就会发生竞态问题(数据竞态),go语言可以使用互斥锁来解决这个问题,互斥锁是一种常用的控制共享资源访问的方法,它能够保证同时只有一个goroutine可以访问共享资源,可以使用sync包的Mutex类型来实现互斥锁,其他的goroutine则在等待锁,当互斥锁释放后,等待的goroutine才可以获取锁进入临界区,多个goroutine同时等待一个锁时,唤醒的策略是随机的,下面是一个例子,如果没有加锁那么最终的结果是错误的,其实就是线程安全问题,多个线程同时对全局变量进行修改这样就会影响另外的线程:
package main
import (
"fmt"
"sync"
)
var x int64
var wg sync.WaitGroup
var lock sync.Mutex
func add() {
for i := 0; i < 5000000; i++ {
lock.Lock() // 加锁
x = x + 1
lock.Unlock() // 解锁
}
wg.Done()
}
func main() {
wg.Add(2)
go add()
go add()
wg.Wait()
fmt.Println(x)
}
读写互斥锁
互斥锁是完全互斥的,但是有很多时候是读多写少的,当我们并发的去读取一个资源不涉及资源修改的时候没有必要加锁,所以这种情况下使用读写锁是更好的一种选择,在Go语言中可以使用sync包中的RWMutex类型,读写锁分为两种:读锁和写锁。当一个goroutine获取读锁之后,其他的goroutine如果是获取读锁会继续获得锁,如果是获取写锁就会等待,当一个goroutine获取写锁之后,其他的goroutine无论是获取读锁还是写锁都会等待,下面是一个例子:
package main
import (
"fmt"
"sync"
"time"
)
var (
x int64
wg sync.WaitGroup
rwlock sync.RWMutex
)
func write() {
rwlock.Lock() // 加写锁
x = x + 1
time.Sleep(10 * time.Millisecond) // 假设读操作耗时10毫秒
rwlock.Unlock() // 解写锁
wg.Done()
}
func read() {
rwlock.RLock() // 加读锁
time.Sleep(time.Millisecond) // 假设读操作耗时1毫秒
rwlock.RUnlock() // 解读锁
wg.Done()
}
func main() {
start := time.Now()
// 使用go关键字启动10个写的协程
for i := 0; i < 10; i++ {
wg.Add(1)
// 启动一个协程
go write()
}
// 使用go关键字启动1000个读的协程
for i := 0; i < 1000; i++ {
wg.Add(1)
go read()
}
wg.Wait()
end := time.Now()
fmt.Println(end.Sub(start))
}