总结笔记自 Go语言中文网
sync包提供了基本的同步基元,如互斥锁。
除了Once和WaitGroup类型,大部分都是适用于低水平程序线程,高水平的同步使用channel通信更好一些。
1. type Locker
type Locker interface {
Lock()
Unlock()
}
Locker接口代表一个可以加锁和解锁的对象。
2. type Once
type Once struct {
// 包含隐藏或非导出字段
}
Once是只执行一次动作的对象。
Once能确保实例化对象Do方法在多线程环境只运行一次,内部通过互斥锁实现
func (*Once) Do
func (o *Once) Do(f func())
官方解释:
Do方法当且仅当第一次被调用时才执行函数f。换句话说,给定变量:
var once Once
如果once.Do(f)
被多次调用,只有第一次调用会执行f,即使f每次调用Do 提供的f值不同。需要给每个要执行仅一次的函数都建立一个Once类型的实例。
Do用于必须刚好运行一次的初始化。因为f是没有参数的,因此可能需要使用闭包来提供给Do方法调用:
config.once.Do(func() { config.init(filename) })
因为只有f返回后Do方法才会返回,f若引起了Do的调用,会导致死锁。
自己写的demo
func main() {
var onlyOnce sync.Once
var a int
for i := 0; i < 1000; i++ {
go onlyOnce.Do(func() {
a++
})
}
time.Sleep(10*time.Second)
fmt.Println(a)
}
结果a=1,Do 方法就是不管几次调用里面的方法,只执行一次动作对象f
注意因为是协程,所以不一定是i=0的时候是第一次执行的
这里不能用WaitGroup等待所有协程结束,因为f只执行一次,Done也就只执行一次
3. type Mutex
type Mutex struct {
// 包含隐藏或非导出字段
}
Mutex是一个互斥锁,可以创建为其他结构体的字段;零值为解锁状态。Mutex类型的锁和线程无关,可以由不同的线程加锁和解锁。
func (*Mutex) Lock
func (m *Mutex) Lock()
Lock方法锁住m,如果m已经加锁,则阻塞直到m解锁
func (*Mutex) Unlock
func (m *Mutex) Unlock()
Unlock方法解锁m,如果m未加锁会导致运行时错误。锁和线程无关,可以由不同的线程加锁和解锁
未上锁demo
func main() {
//var m sync.Mutex
var a int
wg := sync.WaitGroup{}
for i:=0; i<10000; i++ {
wg.Add(1)
go func() {a++; wg.Done()}()
}
wg.Wait()
fmt.Println(a)
} // a的值不到10000
上锁demo
func main() {
var m sync.Mutex
var a int
wg := sync.WaitGroup{}
for i:=0; i<10000; i++ {
wg.Add(1)
go func() {
m.Lock()
a++
m.Unlock()
wg.Done()
}()
}
wg.Wait()
fmt.Println(a) // 不管运行几次, a的值都是10000
}
func (*RWMutex) RLock
func (rw *RWMutex) RLock()
RLock方法将rw锁定为读取状态,禁止其他线程写入,但不禁止读取。
func (*RWMutex) RUnlock
func (rw *RWMutex) RUnlock()
Runlock方法解除rw的读取锁状态,如果m未加读取锁会导致运行时错误。
4. type WaitGroup
type WaitGroup struct {
// 包含隐藏或非导出字段
}
WaitGroup用于等待一组线程的结束。父线程调用Add方法来设定应等待的线程的数量。每个被等待的线程在结束时应调用Done方法。同时,主线程里可以调用Wait方法阻塞至所有线程结束。
func (*WaitGroup) Add
func (wg *WaitGroup) Add(delta int)
Add方法向内部计数加上delta,delta可以是负数;如果内部计数器变为0,Wait方法阻塞等待的所有线程都会释放,如果计数器小于0,方法panic。注意Add加上正数的调用应在Wait之前,否则Wait可能只会等待很少的线程。一般来说本方法应在创建新的线程或者其他应等待的事件之前调用。
func (*WaitGroup) Done
func (wg *WaitGroup) Done()
Done方法减少WaitGroup计数器的值,应在线程的最后执行。
func (*WaitGroup) Wait
func (wg *WaitGroup) Wait()
Wait方法阻塞直到WaitGroup计数器减为0。
demo
var wg sync.WaitGroup
var urls = []string{
"http://www.golang.org/",
"http://www.google.com/",
"http://www.somestupidname.com/",
}
for _, url := range urls {
// Increment the WaitGroup counter.
wg.Add(1)
// Launch a goroutine to fetch the URL.
go func(url string) {
// Decrement the counter when the goroutine completes.
defer wg.Done()
// Fetch the URL.
http.Get(url)
}(url)
}
// Wait for all HTTP fetches to complete.
wg.Wait()
5. type Pool
type Pool struct {
// 可选参数New指定一个函数在Get方法可能返回nil时来生成一个值
// 该参数不能在调用Get方法时被修改
New func() interface{}
// 包含隐藏或非导出字段
}
Pool是一个可以分别存取的临时对象的集合。
Pool中保存的任何item都可能随时不做通告的释放掉。如果Pool持有该对象的唯一引用,这个item就可能被回收。
Pool可以安全的被多个线程同时使用。
Pool的目的是缓存申请但未使用的item用于之后的重用,以减轻GC的压力。也就是说,让创建高效而线程安全的空闲列表更容易。但Pool并不适用于所有空闲列表。
Pool的合理用法是用于管理一组静静的被多个独立并发线程共享并可能重用的临时item。Pool提供了让多个线程分摊内存申请消耗的方法。
Pool的一个好例子在fmt包里。该Pool维护一个动态大小的临时输出缓存仓库。该仓库会在过载(许多线程活跃的打印时)增大,在沉寂时缩小。
另一方面,管理着短寿命对象的空闲列表不适合使用Pool,因为这种情况下内存申请消耗不能很好的分配。这时应该由这些对象自己实现空闲列表。
func (*Pool) Get
func (p *Pool) Get() interface{}
Get方法从池中选择任意一个item,删除其在池中的引用计数,并提供给调用者。Get方法也可能选择无视内存池,将其当作空的。调用者不应认为Get的返回这和传递给Put的值之间有任何关系。
假使Get方法没有取得item:如p.New非nil,Get返回调用p.New的结果;否则返回nil。
func (*Pool) Put
func (p *Pool) Put(x interface{})
Put方法将x放入池中。