Mutex: 解决资源并发访问问题

73 阅读2分钟

同步原语

Mutex, RWMutex, WaitGroup, Cond, Channel

适用场景

  • 共享资源
  • 任务编排. goroutine之间有着依赖关系,常使用WaitGroup和Channel来实现
  • 消息传递. 不同的go routine之间的数据交流. channel来实现.

Mutex基本用法

互斥变量 前Lock,后Unlock

且sync.Mutex不需要初始化

package main


    import (
        "fmt"
        "sync"
    )


    func main() {
        // 互斥锁保护计数器
        var mu sync.Mutex
        // 计数器的值
        var count = 0
        
        // 辅助变量,用来确认所有的goroutine都完成
        var wg sync.WaitGroup
        wg.Add(10)

        // 启动10个gourontine
        for i := 0; i < 10; i++ {
            go func() {
                defer wg.Done()
                // 累加10万次
                for j := 0; j < 100000; j++ {
                    mu.Lock()
                    count++
                    mu.Unlock()
                }
            }()
        }
        wg.Wait()
        fmt.Println(count)
    }

嵌入到struct当中

type Counter struct {
  mu sync.Mutex
  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 j := 0; j < 100000; j++ {
              counter.Lock()
              counter.Count++
              counter.Unlock()
          }
      }()
  }
  wg.Wait()
  fmt.Println(counter.Count)
}


type Counter struct {
  sync.Mutex
  Count uint64
}

进一步封装成线程安全的结构体和方法


func main() {
    // 封装好的计数器
    var counter Counter

    var wg sync.WaitGroup
    wg.Add(10)

    // 启动10个goroutine
    for i := 0; i < 10; i++ {
        go func() {
            defer wg.Done()
            // 执行10万次累加
            for j := 0; j < 100000; j++ {
                counter.Incr() // 受到锁保护的方法
            }
        }()
    }
    wg.Wait()
    fmt.Println(counter.Count())
}

// 线程安全的计数器类型
type Counter struct {
    CounterType int
    Name        string

    mu    sync.Mutex
    count uint64
}

// 加1的方法,内部使用互斥锁保护
func (c *Counter) Incr() {
    c.mu.Lock()
    c.count++
    c.mu.Unlock()
}

// 得到计数器的值,也需要锁保护
func (c *Counter) Count() uint64 {
    c.mu.Lock()
    defer c.mu.Unlock()
    return c.count
}

错误场景

lock与unlock没有成对出现

将lock传参

会传递一个带状态的lock

type Counter struct {
   sync.Mutex
   Count int
}

func main() {
   var c Counter
   c.Lock()
   defer c.Unlock()
   c.Count++
   foo(c) // 复制锁
}

// 这里Counter的参数是通过复制的方式传入的
func foo(c Counter) {
   c.Lock()
   defer c.Unlock()
   fmt.Println("in foo")
}

相互等待


package main


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


func main() {
    // 派出所证明
    var psCertificate sync.Mutex
    // 物业证明
    var propertyCertificate sync.Mutex


    var wg sync.WaitGroup
    wg.Add(2) // 需要派出所和物业都处理


    // 派出所处理goroutine
    go func() {
        defer wg.Done() // 派出所处理完成


        psCertificate.Lock()
        defer psCertificate.Unlock()


        // 检查材料
        time.Sleep(5 * time.Second)
        // 请求物业的证明
        propertyCertificate.Lock()
        propertyCertificate.Unlock()
    }()


    // 物业处理goroutine
    go func() {
        defer wg.Done() // 物业处理完成


        propertyCertificate.Lock()
        defer propertyCertificate.Unlock()


        // 检查材料
        time.Sleep(5 * time.Second)
        // 请求派出所的证明
        psCertificate.Lock()
        psCertificate.Unlock()
    }()


    wg.Wait()
    fmt.Println("成功完成")
}

RWMutex

方法

Lock/Unlock : 写操作时调用的方法 RLock/RUlock : 读操作时调用的方法

基本使用


func main() {
    var counter Counter
    for i := 0; i < 10; i++ { // 10个reader
        go func() {
            for {
                counter.Count() // 计数器读操作
                time.Sleep(time.Millisecond)
            }
        }()
    }

    for { // 一个writer
        counter.Incr() // 计数器写操作
        time.Sleep(time.Second)
    }
}
// 一个线程安全的计数器
type Counter struct {
    mu    sync.RWMutex
    count uint64
}

// 使用写锁保护
func (c *Counter) Incr() {
    c.mu.Lock()
    c.count++
    c.mu.Unlock()
}

// 使用读锁保护
func (c *Counter) Count() uint64 {
    c.mu.RLock()
    defer c.mu.RUnlock()
    return c.count
}