sync包提供了基本的同步基元,如互斥锁。除了Once和WaitGroup类型,大部分都是适用于低水平程序线程,高水平的同步使用channel通信更好一些。
type Locker
Locker接口代表一个可以加锁和解锁的对象
type Locker interface {
Lock()
Unlock()
}
type Once
Once是只执行一次动作的对象。
func (o *Once) Do(f func()) :Do方法当且仅当第一次被调用时才执行函数f
var once sync.Once
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
}
//Only once 只打印了一次
type WaitGroup
func (wg WaitGroup) Add(delta int) //Add方法向内部计数加上delta
func (wg WaitGroup) Done() //Done方法减少WaitGroup计数器的值,应在线程的最后执行。
func (wg *WaitGroup) Wait() //Wait方法阻塞直到WaitGroup计数器减为0。
func cal(a int , b int ,n *sync.WaitGroup) {
c := a+b
fmt.Printf("%d + %d = %d\n",a,b,c)
defer n.Done() //goroutinue完成后, WaitGroup的计数-1
}
func main() {
var go_sync sync.WaitGroup //声明一个WaitGroup变量
for i :=0 ; i<10 ;i++{
go_sync.Add(1) // WaitGroup的计数加1
go cal(i,i+1,&go_sync)
}
go_sync.Wait() //等待所有goroutine执行完毕
}
type Mutex
func (m Mutex) Lock() //Lock方法锁住m,如果m已经加锁,则阻塞直到m解锁。
func (m Mutex) Unlock() //Unlock方法解锁m,如果m未加锁会导致运行时错误。锁和线程无关,可以由不同的线程加锁和解锁。
func sum(a *int,go_sync *sync.WaitGroup,go_mu *sync.Mutex) {
for i:=0;i<50000;i++{
go_mu.Lock()
*a=*a+1
go_mu.Unlock()
}
go_sync.Done()
}
func main() {
var go_sync sync.WaitGroup
var go_mu sync.Mutex
var a =0
go_sync.Add(2)
go sum(&a,&go_sync,&go_mu)
go sum(&a,&go_sync,&go_mu)
go_sync.Wait()
fmt.Println(a)
}
type RWMutex
func (rw RWMutex) Lock() //Lock方法将rw锁定为写入状态,禁止其他线程读取或者写入。
func (rw RWMutex) Unlock() //Unlock方法解除rw的写入锁状态,如果m未加写入锁会导致运行时错误。
func (rw RWMutex) RLock() //RLock方法将rw锁定为读取状态,禁止其他线程写入,但不禁止读取。
func (rw RWMutex) RUnlock() //Runlock方法解除rw的读取锁状态,如果m未加读取锁会导致运行时错误。
var wg sync.WaitGroup
var rw sync.RWMutex
var(
i=1
)
func write(){
rw.Lock()
i++
time.Sleep(time.Second)
rw.Unlock()
wg.Done()
}
func read(){
fmt.Println("正在等待写锁释放")
rw.RLock()
fmt.Println(i)
time.Sleep(time.Second) //读锁是并发的,这里不会等待3s
rw.RUnlock()
wg.Done()
}
func main() {
go write()
wg.Add(1)
for i:=0;i<3;i++{
wg.Add(1)
go read()
}
wg.Wait()
}
正在等待写锁释放
正在等待写锁释放
正在等待写锁释放
2
2
2
type Cond
type Cond struct {
// 在观测或更改条件时L会冻结
L Locker
// 包含隐藏或非导出字段
}
func NewCond(l Locker) *Cond
// 唤醒所有等待c的线程,调用本方法时,建议(非必须)保持c.L的锁定。
func (c *Cond) Broadcast()
// 唤醒等待c的一个线程,调用本方法时,建议(非必须)保持c.L的锁定。
// 按等待队列先进先出的顺序唤醒
func (c *Cond) Signal()
// Wait自行解锁c.L并阻塞当前线程,在之后线程恢复执行时,Wait方法会在返回前锁定c.L。
func (c *Cond) Wait()
/*
c.L.Lock()
for !condition() {
c.Wait()
}
... make use of condition ...
c.L.Unlock()
*/
type Pool
Pool可以安全的被多个线程同时使用**Pool就是为了减少GC压力的, 重复利用内存
Get与Put的之间没有任何关系
pool里的对象随时都有可能被自动移除,并且没有任何通知
type Pool struct {
// 可选参数New指定一个函数在Get方法可能返回nil时来生成一个值
// 该参数不能在调用Get方法时被修改
New func() interface{}
// 包含隐藏或非导出字段
}
func (p Pool) Get() interface{}:从池中选择任意一个item,删除其在池中的引用计数,并提供给调用者
func (p Pool) Put(x interface{}):Put方法将x放入池中
个人认为的正确姿势
package abc
import (
"sync"
)
type User struct {
Name string
Age int8
}
var pool = sync.Pool{
New: func() interface{} {
return new(User)
},
}
func UsePool() {
for i:=0;i<10000000;i++{
user1:=pool.Get().(*User)
user1.Name = "tom"
user1.Age = 20
。。。。。。
pool.Put(user1)//注意使用完put回去,不然会一直new,增加内存开销
}
}
基准测试
package main
import (
"sync"
"testing"
)
type Small struct {
a int
}
var pool = sync.Pool{
New: func() interface{} { return new(Small) },
}
//go:noinline
func inc(s *Small) { s.a++ }
func BenchmarkWithoutPool(b *testing.B) {
var s *Small
for i := 0; i < b.N; i++ {
for j := 0; j < 1000; j++ {
s = &Small{ a: 1, }
inc(s)
}
}
}
func BenchmarkWithPool(b *testing.B) {
var s *Small
for i := 0; i < b.N; i++ {
for j := 0; j < 1000; j++ {
s = pool.Get().(*Small)
s.a = 1
pool.Put(s)
}
}
}
type Map
func (m Map) Store(key, value interface{}):增
func (m Map) Delete(key interface{}):删
func (m *Map) Load(key interface{}) (value interface{}, ok bool)
- 读取对应key的值,ok表示是否在map中查询到key
func (m *Map) LoadOrStore(key, value interface{}) (actual interface{}, loaded bool)
- 针对某个key的存在读取不存在就存储,loaded为true表示存在值,false表示不存在值
func (m *Map) LoadAndDelete(key interface{}) (value interface{}, loaded bool)
- 删除并返回key,false表示不存在值
func (m *Map) Range(f func(key, value interface{}) bool)
- 表示对所有key进行遍历,并将遍历出的key,value传入回调函数进行函数调用,回调函数返回false时遍历结束,否则遍历完所有key. ```go package main import ( “sync” “fmt” )
func main() { //开箱即用 var sm sync.Map //store 方法,添加元素 sm.Store(1,”a”) //Load 方法,获得value if v,ok:=sm.Load(1);ok{ fmt.Println(v) } //LoadOrStore方法,获取或者保存 //参数是一对key:value,如果该key存在且没有被标记删除则返回原先的value(不更新)和true;不存在则store,返回该value 和false if vv,ok:=sm.LoadOrStore(1,”c”);ok{ fmt.Println(vv) } if vv,ok:=sm.LoadOrStore(2,”c”);!ok{ fmt.Println(vv) } //遍历该map,参数是个函数,该函数参的两个参数是遍历获得的key和value, 返回一个bool值,当返回false时,遍历立刻结束。 sm.Range(func(k,v interface{})bool{ fmt.Print(k) fmt.Print(“:”) fmt.Print(v) fmt.Println() return true }) } a a c 1:a 2:c ```
可见随着cpu核心数的增加、并发加剧,这种读写锁+map的方式性能在不停的衰减,并且在核数为4的时候出现了性能的拐点;而sync.Map虽然性能不是特别好,但是相对比较平稳。