概述
sync包提供了基本的同步基元,如互斥锁。除了Once和WaitGroup类型,大部分都是适用于低水平程序线程,高水平的同步使用channel通信更好一些。
本包的类型的值不应被拷贝。
虽然文档解释可能不够深入,或者浅显易懂,但是我觉得还是贴出来,对比了解可能会更好。
Go语言中实现并发或者是创建一个goroutine很简单,只需要在函数前面加上"go",就可以了,那么并发中,如何实现多个goroutine之间的同步和通信?答: channel 我是第一个想到的, sync, 原子操作atomic等都可以。
首先我们先来介绍一下sync包下的各种类型。那么我们先来罗列一下sync包下所有的类型吧。
1. Cond 条件等待
type Cond struct {
// L is held while observing or changing the condition
L Locker
// contains filtered or unexported fields
}
解释:
Cond实现了一个条件变量,一个线程集合地,供线程等待或者宣布某事件的发生。
每个Cond实例都有一个相关的锁(一般是*Mutex或*RWMutex类型的值),它必须在改变条件时或者调用Wait方法时保持锁定。Cond可以创建为其他结构体的字段,Cond在开始使用后不能被拷贝。
条件等待通过Wait让例程等待,通过Signal让一个等待的例程继续,通过Broadcase让所有等待的继续。
在Wait之前需要手动为c.L上锁, Wait结束了手动解锁。为避免虚假唤醒, 需要将Wait放到一个条件判断的循环中,官方要求写法:
c.L.Lock()
for !condition() {
c.Wait()
}
// 执行条件满足之后的动作...
c.L.Unlock()
成员文档:
type Cond struct {
L Locker // 在“检查条件”或“更改条件”时 L 应该锁定。
}
// 创建一个条件等待
func NewCond(l Locker) *Cond
// Broadcast 唤醒所有等待的 Wait,建议在“更改条件”时锁定 c.L,更改完毕再解锁。
func (c *Cond) Broadcast()
// Signal 唤醒一个等待的 Wait,建议在“更改条件”时锁定 c.L,更改完毕再解锁。
func (c *Cond) Signal()
// Wait 会解锁 c.L 并进入等待状态,在被唤醒时,会重新锁定 c.L
func (c *Cond) Wait()
代码示例:
package main
import (
"fmt"
"sync"
"time"
)
func main() {
condition := false // 条件不满足
var mu sync.Mutex
cond := sync.NewCond(&mu) // 创建一个Cond
//让协程去创造条件
go func() {
mu.Lock()
condition = true // 改写条件
time.Sleep(3 * time.Second)
cond.Signal() // 发送通知:条件ok
mu.Unlock()
}()
mu.Lock()
// 检查条件是否满足,避免虚假通知,同时避免 Signal 提前于 Wait 执行。
for !condition { // 如果Signal提前执行了,那么此处就是false了
// 等待条件满足的通知,如果虚假通知,则继续循环等待
cond.Wait() // 等待时 mu 处于解锁状态,唤醒时重新锁定。 (阻塞当前线程)
}
fmt.Println("条件满足,开始后续动作...")
mu.Unlock()
}
2. Locker
type Locker interface {
Lock()
Unlock()
}
Locker接口代表一个可以加锁和解锁的对象。 是一个接口。
3. Mutex 互斥锁
type Mutex struct {
// contains filtered or unexported fields
}
解释:
Mutex 是互斥锁。Mutex 的零值是一个解锁的互斥锁。 第一次使用后不得复制 Mutex 。
互斥锁是用来保证在任一时刻, 只能有一个例程访问某个对象。 Mutex的初始值为解锁的状态。 通常作为其他结构体的你名字段使用, 并且可以安全的在多个例程中并行使用。
成员文档:
// Lock 用于锁住 m,如果 m 已经被加锁,则 Lock 将被阻塞,直到 m 被解锁。
func (m *Mutex) Lock()
// Unlock 用于解锁 m,如果 m 未加锁,则该操作会引发 panic。
func (m *Mutex) Unlock()
代码示例:
package main
import (
"fmt"
"sync"
)
type SafeInt struct {
sync.Mutex
Num int
}
func main() {
waitNum := 10 // 设置等待的个数(继续往下看)
count := SafeInt{}
done := make(chan bool)
for i := 0; i < waitNum; i++ {
go func(i int) {
count.Lock() // 加锁,防止其它例程修改 count
count.Num = count.Num + i
fmt.Print(count.Num, " ")
count.Unlock()
done <- true
}(i)
}
for i := 0; i < waitNum; i++ {
<-done
}
}
[ `go run sync_mutex.go` | done: 216.47974ms ]
1 4 8 8 10 15 21 30 37 45
注意:多次输出结果不一致, 试想为什么会出现10个结果中有0值得, 为什么10个结果中都大于0呢?或者都大于1呢? 那么会不会出现10个结果中最小值是9 呢?
4. Once 单次执行
type Once struct {
// contains filtered or unexported fields
}
解释:
Once是只执行一次动作的对象。
Once 的作用是多次调用但只执行一次,Once 只有一个方法,Once.Do(),向 Do 传入一个函数,这个函数在第一次执行 Once.Do() 的时候会被调用,以后再执行 Once.Do() 将没有任何动作,即使传入了其它的函数,也不会被执行,如果要执行其它函数,需要重新创建一个 Once 对象。
成员文档:
// 多次调用仅执行一次指定的函数 f
func (o *Once) Do(f func())
代码示例:
package main
// 官方案例
import (
"fmt"
"sync"
)
func main() {
var once sync.Once
var num int
onceBody := func() {
fmt.Println("Only once")
}
done := make(chan bool)
for i := 0; i < 10; i++ {
go func() {
once.Do(onceBody) // 多次调用
done <- true
}()
}
for i := 0; i < 10; i++ {
<-done
}
}
待续...
有疑问加站长微信联系(非本文作者)