Skip to content

Mutex

我们已经看到通道在 goroutine 之间通信方面非常出色。

但如果我们不需要通信呢?如果我们只是想确保同一时间只有一个 goroutine 能访问某个变量,以避免冲突呢?

这个概念称为 互斥 ,而提供它的数据结构的惯用名称是互斥锁

Go标准库通过sync.Mutex及其两个方法(LockUnlock)提供互斥锁。

一般将Mutex写在结构体里面:

go
type SafeCounter struct {
	mu sync.Mutex
	v  map[string]int
}

Mutex锁的作用域实际上并不是以结构体为单位的,它的保护范围是上锁和解锁代码之间访问到的数据。

具体实现规则是:一旦有一个协程执行lock后,后续代码访问到的数据都会被上锁,同一时间其他也要访问这些数据并执行lock的协程会被阻塞,直到unlock被执行。

示例

go
var wait sync.WaitGroup
var count = 0

var lock sync.Mutex

func main() {
	wait.Add(10)
	for i := 0; i < 10; i++ {
		go func(data *int) {
			// 加锁
			lock.Lock()
			// 模拟访问耗时
			time.Sleep(time.Millisecond * time.Duration(rand.Intn(1000)))
			// 访问数据
			temp := *data
			// 模拟计算耗时
			time.Sleep(time.Millisecond * time.Duration(rand.Intn(1000)))
			ans := 1
			// 修改数据
			*data = temp + ans
			// 解锁
			lock.Unlock()
			fmt.Println(*data)
			wait.Done()
		}(&count)
	}
	wait.Wait()
	fmt.Println("最终结果", count)
}

读写锁

读写互斥锁sync.RWMutex

  • 如果获得了读锁,其他协程进行写操作时会阻塞,其他协程进行读操作时不会阻塞
  • 如果获得了写锁,其他协程进行写操作时会阻塞,其他协程进行读操作时会阻塞
go
// 加读锁
func (rw *RWMutex) RLock() 

// 尝试加读锁
func (rw *RWMutex) TryRLock() bool 

// 解读锁
func (rw *RWMutex) RUnlock() 

// 加写锁
func (rw *RWMutex) Lock()

// 尝试加写锁
func (rw *RWMutex) TryLock() bool 

// 解写锁
func (rw *RWMutex) Unlock()

更安全的写法

go
c.mu.Lock()
defer c.mu.Unlock()   // 确保函数返回/panic 时一定释放锁
c.v[key]++