前言
Go语言在设计上对同步(Synchronization,数据同步和线程同步)提供大量的支持,比如 goroutine和channel同步原语,库层面有
- sync:提供基本的同步原语(比如Mutex、RWMutex、Locker)和 工具类(Once、WaitGroup、Cond、Pool、Map)
- sync/atomic:提供变量的原子操作(基于硬件指令 compare-and-swap)
注意:当我说“类”时,是指 Go 里的 struct(单身狗要有面向“对象”编程的觉悟)。
Go语言里对同步的支持主要有五类应用场景:
- 资源独占:当多个线程依赖同一份资源(比如数据),需要同时读/写同一个内存地址时,runtime需要保证只有一个修改这份数据,并且保证该修改对其他线程可见。锁和变量的原子操作为此而设计
- 生产者-消费者:在生产者-消费者模型中,消费者依赖生产者产出数据。 channel(管道) 为此而设计
- 懒加载:一个资源,当且仅当第一次执行一个操作时,该操作执行过程中其他的同类操作都会被阻塞,直到该操作完成。sync.Once为此而设计
- fork-join:一个任务首先创建出N个子任务,N个子任务全部执行完成以后,主任务搜集结果,执行后续操作。sync.WaitGroup 为此而设计
- 条件变量:条件变量是一个同步原语,可以同时阻塞多个线程,直到另一个线程 1) 修改了条件; 2)通知一个(或所有)等待的线程。sync.Cond 为此而设计
注意:这里当我说"线程"时,了解Go的同学可以自动映射到 "goroutine"(协程)。
关于 1和2,通过官方文档了解其用法和实现。本系列的主角是 sync 下的工工具类,从 sync.Once 开始。内容分两部分:
- sync.Once 用法
- sync.Once 实现
sync.Once 用法
在多数情况下,sync.Once 被用于控制变量的初始化,这个变量的读写通常遵循单例模式,满足这三个条件:
- 当且仅当第一次读某个变量时,进行初始化(写操作)
- 变量被初始化过程中,所有读都被阻塞(读操作;当变量初始化完成后,读操作继续进行
- 变量仅初始化一次,初始化完成后驻留在内存里
在 net 库里,系统的网络配置就是存放在一个变量里,代码如下:
package net
var (
// guards init of confVal via initConfVal
confOnce sync.Once
confVal = &conf{goos: runtime.GOOS}
)
// systemConf returns the machine's network configuration.
func systemConf() *conf {
confOnce.Do(initConfVal)
return confVal
}
func initConfVal() {
dnsMode, debugLevel := goDebugNetDNS()
confVal.dnsDebugLevel = debugLevel
// 省略部分代码...
}
confValconfOnceconfOnceconfOnce.DoDo
func (o *Once) Do(f func())
Do
once.Sync
- 初始化 rpc/http client
- open/close 文件
- close channel
- 线程池初始化
once.Sync
// source: os/exec/exec.go
package exec
type closeOnce struct {
*os.File
once sync.Once
err error
}
func (c *closeOnce) Close() error {
c.once.Do(c.close)
return c.err
}
func (c *closeOnce) close() {
c.err = c.File.Close()
}
sync.Once 实现
exactly once
package sync
import "sync/atomic"
type Once struct {
done uint32
m Mutex
}
func (o *Once) Do(f func()) {
if atomic.LoadUint32(&o.done) == 0 {
o.m.Lock()
defer o.m.Unlock()
if o.done == 0 {
defer atomic.StoreUint32(&o.done, 1)
f()
}
}
}
done
donef
m Mutexf
donem.Lock()
发散一下
lazydonesynchronizedm Mutex
lazy
References
扫码关注微信公众号“深入Go语言”