sync包提供了基本的同步基元,如互斥锁。除了Once和WaitGroup类型,大部分都是适用于低水平程序线程,高水平的同步使用channel通信更好一些。

type Locker

Locker接口代表一个可以加锁和解锁的对象

  1. type Locker interface {
  2. Lock()
  3. Unlock()
  4. }

type Once

Once是只执行一次动作的对象。
func (o *Once) Do(f func()) :Do方法当且仅当第一次被调用时才执行函数f

  1. var once sync.Once
  2. onceBody := func() {
  3. fmt.Println("Only once")
  4. }
  5. done := make(chan bool)
  6. for i := 0; i < 10; i++ {
  7. go func() {
  8. once.Do(onceBody)
  9. done <- true
  10. }()
  11. }
  12. for i := 0; i < 10; i++ {
  13. <-done
  14. }
  15. //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。

  1. func cal(a int , b int ,n *sync.WaitGroup) {
  2. c := a+b
  3. fmt.Printf("%d + %d = %d\n",a,b,c)
  4. defer n.Done() //goroutinue完成后, WaitGroup的计数-1
  5. }
  6. func main() {
  7. var go_sync sync.WaitGroup //声明一个WaitGroup变量
  8. for i :=0 ; i<10 ;i++{
  9. go_sync.Add(1) // WaitGroup的计数加1
  10. go cal(i,i+1,&go_sync)
  11. }
  12. go_sync.Wait() //等待所有goroutine执行完毕
  13. }

type Mutex

func (m Mutex) Lock() //Lock方法锁住m,如果m已经加锁,则阻塞直到m解锁。
func (m
Mutex) Unlock() //Unlock方法解锁m,如果m未加锁会导致运行时错误。锁和线程无关,可以由不同的线程加锁和解锁。

  1. func sum(a *int,go_sync *sync.WaitGroup,go_mu *sync.Mutex) {
  2. for i:=0;i<50000;i++{
  3. go_mu.Lock()
  4. *a=*a+1
  5. go_mu.Unlock()
  6. }
  7. go_sync.Done()
  8. }
  9. func main() {
  10. var go_sync sync.WaitGroup
  11. var go_mu sync.Mutex
  12. var a =0
  13. go_sync.Add(2)
  14. go sum(&a,&go_sync,&go_mu)
  15. go sum(&a,&go_sync,&go_mu)
  16. go_sync.Wait()
  17. fmt.Println(a)
  18. }

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未加读取锁会导致运行时错误。

  1. var wg sync.WaitGroup
  2. var rw sync.RWMutex
  3. var(
  4. i=1
  5. )
  6. func write(){
  7. rw.Lock()
  8. i++
  9. time.Sleep(time.Second)
  10. rw.Unlock()
  11. wg.Done()
  12. }
  13. func read(){
  14. fmt.Println("正在等待写锁释放")
  15. rw.RLock()
  16. fmt.Println(i)
  17. time.Sleep(time.Second) //读锁是并发的,这里不会等待3s
  18. rw.RUnlock()
  19. wg.Done()
  20. }
  21. func main() {
  22. go write()
  23. wg.Add(1)
  24. for i:=0;i<3;i++{
  25. wg.Add(1)
  26. go read()
  27. }
  28. wg.Wait()
  29. }
  30. 正在等待写锁释放
  31. 正在等待写锁释放
  32. 正在等待写锁释放
  33. 2
  34. 2
  35. 2

type Cond

  1. type Cond struct {
  2. // 在观测或更改条件时L会冻结
  3. L Locker
  4. // 包含隐藏或非导出字段
  5. }
  6. func NewCond(l Locker) *Cond
  7. // 唤醒所有等待c的线程,调用本方法时,建议(非必须)保持c.L的锁定。
  8. func (c *Cond) Broadcast()
  9. // 唤醒等待c的一个线程,调用本方法时,建议(非必须)保持c.L的锁定。
  10. // 按等待队列先进先出的顺序唤醒
  11. func (c *Cond) Signal()
  12. // Wait自行解锁c.L并阻塞当前线程,在之后线程恢复执行时,Wait方法会在返回前锁定c.L。
  13. func (c *Cond) Wait()
  14. /*
  15. c.L.Lock()
  16. for !condition() {
  17. c.Wait()
  18. }
  19. ... make use of condition ...
  20. c.L.Unlock()
  21. */

type Pool

Pool可以安全的被多个线程同时使用**Pool就是为了减少GC压力的, 重复利用内存
Get与Put的之间没有任何关系
pool里的对象随时都有可能被自动移除,并且没有任何通知

  1. type Pool struct {
  2. // 可选参数New指定一个函数在Get方法可能返回nil时来生成一个值
  3. // 该参数不能在调用Get方法时被修改
  4. New func() interface{}
  5. // 包含隐藏或非导出字段
  6. }

func (p Pool) Get() interface{}:从池中选择任意一个item,删除其在池中的引用计数,并提供给调用者
func (p
Pool) Put(x interface{}):Put方法将x放入池中
个人认为的正确姿势

  1. package abc
  2. import (
  3. "sync"
  4. )
  5. type User struct {
  6. Name string
  7. Age int8
  8. }
  9. var pool = sync.Pool{
  10. New: func() interface{} {
  11. return new(User)
  12. },
  13. }
  14. func UsePool() {
  15. for i:=0;i<10000000;i++{
  16. user1:=pool.Get().(*User)
  17. user1.Name = "tom"
  18. user1.Age = 20
  19. 。。。。。。
  20. pool.Put(user1)//注意使用完put回去,不然会一直new,增加内存开销
  21. }
  22. }

基准测试

  1. package main
  2. import (
  3. "sync"
  4. "testing"
  5. )
  6. type Small struct {
  7. a int
  8. }
  9. var pool = sync.Pool{
  10. New: func() interface{} { return new(Small) },
  11. }
  12. //go:noinline
  13. func inc(s *Small) { s.a++ }
  14. func BenchmarkWithoutPool(b *testing.B) {
  15. var s *Small
  16. for i := 0; i < b.N; i++ {
  17. for j := 0; j < 1000; j++ {
  18. s = &Small{ a: 1, }
  19. inc(s)
  20. }
  21. }
  22. }
  23. func BenchmarkWithPool(b *testing.B) {
  24. var s *Small
  25. for i := 0; i < b.N; i++ {
  26. for j := 0; j < 1000; j++ {
  27. s = pool.Get().(*Small)
  28. s.a = 1
  29. pool.Put(s)
  30. }
  31. }
  32. }

image.png

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 ``` image.png

可见随着cpu核心数的增加、并发加剧,这种读写锁+map的方式性能在不停的衰减,并且在核数为4的时候出现了性能的拐点;而sync.Map虽然性能不是特别好,但是相对比较平稳。