昝沛珊 发表于 前天 20:33

Golang基础笔记十五之sync

本文首发于公众号:Hunter后端
原文链接:Golang基础笔记十五之sync
这一篇笔记介绍 Golang 中的 sync 模块。
sync 包主要提供了基础的同步原语,比如互斥锁,读写锁,等待组等,用于解决并发编程中的线程安全问题,以下是本篇笔记目录:

[*]WaitGroup-等待组
[*]sync.Mutex-互斥锁
[*]sync.RWMutex-读写锁
[*]sync.Once-一次性执行
[*]sync.Pool-对象池
[*]sync.Cond-条件变量
[*]sync.Map
1、WaitGroup-等待组

前面在第十篇我们介绍 goroutine 和 channel 的时候,在使用 goroutine 的时候介绍有一段代码如下:
package main

import (
    "fmt"
    "time"
)

func PrintGoroutineInfo() {
    fmt.Println("msg from goroutine")
}

func main() {
    go PrintGoroutineInfo()
    time.Sleep(1 * time.Millisecond)
    fmt.Println("msg from main")
}在这里,我们开启了一个协程调用 PrintGoroutineInfo() 函数,然后使用 time.Sleep() 来等待它调用结束。
然而在开发中,我们不能确定这个函数多久才能调用完毕,也无法使用准确的 sleep 时间来等待,那么这里就可以使用到 sync 模块的 WaitGroup 函数来等待一个或多个 goroutine 执行完毕。
下面是使用示例:
package main

import (
    "fmt"
    "math/rand"
    "sync"
    "time"
)

func SleepRandSeconds(wg *sync.WaitGroup) {
    defer wg.Done()
    sleepSeconds := rand.Intn(3)
    fmt.Printf("sleep %d seconds\n", sleepSeconds)
    time.Sleep(time.Duration(sleepSeconds) * time.Second)
}

func main() {
    var wg sync.WaitGroup

    wg.Add(2)
    go SleepRandSeconds(&wg)
    go SleepRandSeconds(&wg)

    wg.Wait()
    fmt.Println("函数执行完毕")
}在这里,我们通过 var wg sync.WaitGroup 定义了一个等待组,并通过 wg.Add(2) 表示添加了需要等待的并发数,在并发中我们将 &wg 传入并通过 wg.Done() 减少需要等待的并发数。
在 wg.Done() 函数内部,使用 wg.Add(-1) 减少需要等待的并发数,在 main 函数中,使用 wg.Wait() 进入阻塞状态,当等待的并发都完成后,此函数就会返回,完成等待并接着往后执行。
2、sync.Mutex-互斥锁

1. 数据竞态与互斥锁

当多个 goroutine 并发访问同一个共享资源,且至少有一个访问是写操作时,就会发生数据竞态,造成的结果就是程序每次运行的结果表现可能会不一致。
比如下面的示例:
var balance int

func AddFunc() {
    balance += 1
}
func main() {
    for range 100 {
      go AddFunc()
    }
    time.Sleep(5 * time.Second)
    fmt.Println("balance is: ", balance)
}多次执行上面的代码,最终输出的 balance 的值可能都不一致。
如果一个变量在多个 goroutine 同时访问时,不会出现比如数据不一致或程序崩溃的情况,那么我们就称其是并发安全的。
我们可以使用 go run -race main.go 的方式来检测数据竞态,执行检测后,会输出数据竞态的一些信息,比如发生在代码的多少行,一共发生了多少次数据竞态:
==================
WARNING: DATA RACE
Read at 0x000003910df8 by goroutine 7:
main.AddFunc()
      /../main.go:13 +0x24

Previous write at 0x000003910df8 by goroutine 6:
main.AddFunc()
      /../main.go:13 +0x3c

Goroutine 7 (running) created at:
main.main()
      /../main.go:18 +0x32

Goroutine 6 (finished) created at:
main.main()
   /../main.go:18 +0x32
   
==================
balance is:98
Found 3 data race(s)
exit status 66而要避免这种数据竞态的发生,我们可以限制在同一时间只能有一个 goroutine 访问同一个变量,这种方法称为互斥机制。
我们可以通过缓冲通道和 sync.Mutex 来实现这种互斥锁的操作。
2. 缓冲通道实现互斥锁

我们可以通过容量为 1 的缓冲通道来实现互斥锁的操作,保证同一时间只有一个 goroutine 访问同一个变量,下面是修改后的代码:
var sema = make(chan struct{}, 1)var balance intfunc AddFunc() {    sema
页: [1]
查看完整版本: Golang基础笔记十五之sync