Golang笔记|Mutex

61 阅读2分钟

1.概念

Golang中的Mutex是互斥锁,为了保护并发访问导致的意想不到的结果。

2.基本用法

Lock方法: 获取锁
Unlock方法: 释放锁

3. 错误示例

package main

import (
   "fmt"
   "sync"
)

type Counter struct {
   count uint64
}

func main() {
   var counter Counter
   var wg sync.WaitGroup
   wg.Add(10)
   for i := 0; i < 10; i++ {
      go func() {
         defer wg.Done()
         for i := 0; i < 10000; i++ {
            counter.count++
         }

      }()
   }
   wg.Wait()
   fmt.Println(counter.count)
}

上述代码中的输出结果并没有准确的输出100000,原因就是因为在多个goroutine并发执行时,并不会按照顺序执行,因此在争夺count的资源,导致在某一个goroutine的计算结果为200时,被另外一个goroutine争夺资源计算结果更改为100

4. 使用互斥锁Mutex解决

package main

import (
   "fmt"
   "sync"
)

type Counter struct {
   count uint64
   mu    sync.Mutex
}

func (c *Counter) Incr() {
   c.mu.Lock()
   defer c.mu.Unlock()
   c.count++
}

func (c *Counter) Count() uint64 {
   c.mu.Lock()
   defer c.mu.Unlock()
   return c.count
}

func main() {
   var counter Counter
   var wg sync.WaitGroup
   wg.Add(10)
   for i := 0; i < 10; i++ {
      go func() {
         defer wg.Done()
         for i := 0; i < 10000; i++ {
            counter.Incr()
         }

      }()
   }
   wg.Wait()
   fmt.Println(counter.Count())
}

上述代码则能准确的输出100000

5. 锁的争夺模式

正常模式

优势:拥有更好的性能

正常模式下, 锁的争取是遵循先入先出队列,被唤醒的goroutine不会直接获得锁,会与新进来的goroutine进行竞争。新来的goroutine有先天的优势,因为它们正在CPU中运行,可能它们的数量还不少,所以,在高并发情况下,被唤醒的 waiter 可能比较悲剧地获取不到锁,这时就需要饥饿模式

饥饿模式

饥饿模式是对公平性和性能的一种平衡,它避免了某些 goroutine 长时间的等待锁

在饥饿模式下,Mutex 的拥有者将直接把锁交给队列最前面的 waiter。新来的 goroutine 不会尝试获取锁,即使看起来锁没有被持有,它也不会去抢,它会乖乖地加入到等待队列的尾部

6. 模式转换条件

正常模式转为饥饿模式条件:
1.如果获取锁等待的时间超过阈值1毫秒,那么mutex会进入饥饿模式

饥饿模式转为正常模式条件:
1.当前获取锁的goroutine是队列中的最后一个
2.当前获取锁的goroutine等待的时间小于阈值1毫秒