大家好,我是小许,标准库中的sync包在我们的日常开发中用的颇为广泛,那么大家对sync包的用法知道多少呢,这篇文章就大致讲一下sync包和它的使用
Package sync provides basic synchronization primitives such as mutual exclusion locks. Other than the Once and WaitGroup types, most are intended for use by low-level library routines. Higher-level synchronization is better done via channels and communication. Values containing the types defined in this package should not be copied.
这句话大意就是说:sync包提供了基本的同步基元,如互斥锁。除了Once和WaitGroup类型,大部分都是适用于低水平程序线程,高水平的同步使用channel通信更好一些
包中定义了以下类型: Locker, Once, Mutex, RWMutex, WaitGroup, Pool。接下来我们逐个讲每种类型的使用,所有这些在Go sdk中的src/runtime/sync包,可逐个查看,特别是结合test一起
Locker接口,包含Lock()和Unlock()两个方法,用于代表一个能被加锁和解锁的对象.
Locker接口
Lock方法锁住Mutex,如果Mutex已经加锁,则阻塞直到m解锁
1.2 UnLock()
Unlock方法解锁m,如果解锁一个未加锁的mutex会导致运行时错误、锁定m与特定的groutine无关。允许不同的groutine进行加锁、解锁
Once是只执行一次动作的对象,使用后不得复制
Once结构
Once只有一个Do方法
var once Once
func (o *Once) Do(f func())
Do方法当且仅当第一次被调用时才执行函数f。once.Do(f)被多次调用,只有第一次调用会执行f,即使f每次调用Do 提供的f值不同。需要给每个要执行仅一次的函数都建立一个Once类型的实例
Mutex是一个互斥锁,可以创建为其他结构体的字段;零值为解锁状态。Mutex类型的锁和线程无关,可以由不同的线程加锁和解锁。实现了Locker()接口的UnLock()和Locker()方法,同一时刻一段代码只能被一个线程运行
Mutex在大量并发的情况下,会造成锁等待,对性能的影响比较大
Mutex结构
Mutex是一个读写互斥锁,该锁可以被同时多个读取者持有或唯一个写入者持有
有以下方法可使用
WaitGroup 对象内部有一个计数器,最初从0开始,它有三个方法:Add(), Done(), Wait() 用来控制计数器的数量。Add(n) 把计数器设置为n ,Done() 每次把计数器-1 ,wait() 会阻塞代码的运行,直到计数器的值减为0.
//将WaitGroup的计数器增加delta,delta可以为负值。如果WaitGroup的计数器变为0则所有阻塞在Wait()的gorouting将会被释放,如果计数器变为负值则会panic
//Add应该在创建gourouting之前执行,如果重用WaitGroup,必须要等到之前所有Wait都返回后再重新Add。
func (wg *WaitGroup) Add(delta int)
//将WaitGroup计数器减1
func (wg *WaitGroup) Done()
//阻塞等待直至WaitGroup计数器变为0
func (wg *WaitGroup) Wait()
使用示例
func main() {
wg := sync.WaitGroup{}
wg.Add(5)
for i := 0; i < 5; i++ {
go func(i int) {
fmt.Println(i)
wg.Done()
}(i)
}
//阻塞直到所有的groutine都执行完
wg.Wait()
}
Pool是一个可以分别存取的临时对象的集合,可以被看作是一个存放可重用对象的值的容器、过减少GC来提升性能,是Goroutine并发安全的。有两个方法 Get()、Set()
//Get方法没有取得item:如p.New非nil,Get返回调用p.New的结果;否则返回nil
func (p *Pool) Get() interface{}
//将对象放入对象池中
func (p *Pool) Put(x interface{})
//New初始化Pool实例
bufferpool := &sync.Pool {
New: func() interface {} {
println("Create pool instance")
return struct{}{}
}
}
其实在开发使用中我们间接也是使用到了sync.Pool,比如标准库中的fmt、还有gin、iris框架中的Context
newPrinter就是调用的sync.Pool.Get(),拿到pp指针.首先是做了一些format操作。然后调用free()方法,将使用过得pp放回到ppFree中。归还之前将p的部分字段重置,以保证下次调用的是原始pp
//fmt.Printf
func Printf(format string, a ...interface{}) (n int, err error) {
return Fprintf(os.Stdout, format, a...)
}
func Fprintf(w io.Writer, format string, a ...interface{}) (n int, err error) {
p := newPrinter()
...
//使用完后释放,将pp归还给了sync.pool
p.free()
return
}
//返回一个pp结构体指针
func newPrinter() *pp {
p := ppFree.Get().(*pp)
p.panicking = false
p.erroring = false
p.wrapErrs = false
p.fmt.init(&p.buf)
return p
}
var ppFree = sync.Pool{
New: func() interface{} { return new(pp) },
}
//结构体pp
type pp struct {
...
}
iris.New()创建并返回一个空的 iris *Application实例。New()函数中的context.New(),传入一个func,返回一个context.Pool给到 Application的ContextPool。可以看到传入的func实际是给到了sync.Pool.New。app.ContextPool就是存储上下文变量Context的管理池。
Acquire()获取context, Release()释放对象
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。