分享
  1. 首页
  2. 文章

一文看懂golang的sync包

一条大菜狗HS · · 1054 次点击 · · 开始浏览
这是一个创建于 的文章,其中的信息可能已经有所发展或是发生改变。

一文看懂golang的sync包

sync包里包含的struct以及其功能

  • sync.Mutex: 互斥量(锁),主要是处理多个goroutine竞争同一个资源时候的同步问题。
  • sync.RWMutex: 读写互斥量(锁),相对Mutex而言能进行更细腻的控制,主要用在读多写少的情景下。
  • sync.WaitGroup: WaitGroup用于等待一组goroutine结束。
  • sync.Cond:实现一个条件变量,即等待或宣布事件发生的goroutines的会合点。
  • sync.Pool:临时对象池,作为临时对象的保存和复用的集合。
  • sync.Once:顾名思义,Once可以使得函数的调用只执行一次。

具体解释和示例

  • Mutex 主要处理多个goroutine竞争同一个资源时的同步问题,能保证同时只有一个goroutine在使用该资源,而其他的goroutine则在等待,直到占用资源的goroutine释放了Mutex,即是调用了mutex.Unlock()
    示例:
package main
import (
 "fmt"
 "sync"
 "time"
)
func main() {
 ch := make(chan struct{}, 2)
 var l sync.Mutex
 go func() {
 l.Lock()
 defer l.Unlock()
 fmt.Println("goroutine1: lock 2 seconds")
 time.Sleep(time.Second * 2)
 fmt.Println("goroutine1: unlocked")
 ch <- struct{}{}
 }()
 go func() {
 fmt.Println("goroutine2: wait for unlock")
 l.Lock()
 defer l.Unlock()
 fmt.Println("goroutine2: lock 2 seconds")
 ch <- struct{}{}
 }()
 for i:=0;i<2;i++ {
 <- ch
 }
}

执行结果

$ go run main.go
goroutine2: wait for unlock
goroutine1: lock 2 seconds
goroutine1: unlocked
goroutine2: lock 2 seconds

从结果可以看到goroutine2在调用l.Lock()时阻塞了,一直等待goroutine1,直到在goroutine1里调用了l.Unlock()。

  • RWMutex 则可以对锁进行更精细的控制,主要的规则如下:
    • 读锁之间是不互斥的
    • 读锁和写锁互斥
    • 写锁和写锁互斥
      也就意味着同时只有一个goroutine能获得写锁定,当它获得了写锁定了,其他的goroutine,无论是写锁定还是读锁定,都无法获得。而可以同时有一个或多个goroutine获得读锁定,这时其他的goroutine无法获得写锁定。
      示例如下:
package main
import (
 "fmt"
 "math/rand"
 "sync"
 "time"
)
var count int
var rw sync.RWMutex
func main() {
 ch := make(chan struct{}, 10)
 for i:=0;i<5;i++ {
 go read(i, ch)
 }
 
 for i:=0;i<5;i++ {
 <- ch
 }
}
func read(i int, ch chan struct{}) {
 rw.RLock()
 fmt.Printf("goroutine %d 进入读操作\n", i)
 v := count
 time.Sleep(time.Second * 1)
 fmt.Printf("goroutine %d 读取结束,值为:%d\n", i, v)
 rw.RUnlock()
 ch <- struct{}{}
}

示例输出为:

goroutine 4 进入读操作
goroutine 1 进入读操作
goroutine 0 进入读操作
goroutine 2 进入读操作
goroutine 3 进入读操作
goroutine 1 读取结束,值为:0
goroutine 4 读取结束,值为:0
goroutine 3 读取结束,值为:0
goroutine 2 读取结束,值为:0
goroutine 0 读取结束,值为:0

可以看到5个goroutine可以同时进入读操作,而不会有任何的goroutine会阻塞。

对上面的例子稍作调整:

package main
import (
 "fmt"
 "math/rand"
 "sync"
 "time"
)
var count int
var rw sync.RWMutex
func main() {
 ch := make(chan struct{}, 10)
 for i:=0;i<5;i++ {
 go read(i, ch)
 }
 go write(ch)
 for i:=0;i<6;i++ {
 <- ch
 }
}
func read(i int, ch chan struct{}) {
 rw.RLock()
 fmt.Printf("goroutine %d 进入读操作\n", i)
 v := count
 time.Sleep(time.Second * 1)
 fmt.Printf("goroutine %d 读取结束,值为:%d\n", i, v)
 rw.RUnlock()
 ch <- struct{}{}
}
func write(ch chan struct{}) {
 rw.Lock()
 fmt.Printf("goroutine write 进入写操作\n")
 v := rand.Intn(1000)
 count = v
 fmt.Printf("goroutine write 写入结束,新值为:%d\n", count)
 rw.Unlock()
 ch <- struct{}{}
}

加入写锁,输出如下(因为多线程的原因,结果不唯一):

goroutine 1 进入读操作
goroutine 0 进入读操作
goroutine 1 读取结束,值为:0
goroutine 0 读取结束,值为:0
goroutine write 进入写操作
goroutine write 写入结束,新值为:81
goroutine 2 进入读操作
goroutine 3 进入读操作
goroutine 4 进入读操作
goroutine 2 读取结束,值为:81
goroutine 4 读取结束,值为:81
goroutine 3 读取结束,值为:81

可以看到加入了写锁之后,2,3,4这三个goroutine都必须等到goroutine write写入结束才能进入读操作。而0,1这两个goroutine这两个读操作完成之后,才能进入写goroutine。也就说明读写之间是互斥的,而多个读之间是不互斥的。

  • sync.WaitGroup 主要用于等待一组goroutine结束。一般用于控制主线程等待所有的goroutine都结束之后再结束。
    示例如下:
package main
import (
 "fmt"
 "sync"
 "time"
)
func main() {
 wg := sync.WaitGroup{}
 for i:=0;i<5;i++{
 wg.Add(1)
 go func(i int) {
 defer wg.Done()
 fmt.Printf("goroutine %d starts sleep\n", i)
 time.Sleep(time.Second * time.Duration(i))
 fmt.Printf("goroutine %d finished sleep\n", i)
 }(i)
 }
 fmt.Println("wait group waiting...")
 wg.Wait()
 fmt.Println("wait group waitint finished")
}

输出如下:

wait group waiting...
goroutine 1 starts sleep
goroutine 3 starts sleep
goroutine 4 starts sleep
goroutine 0 starts sleep
goroutine 0 finished sleep
goroutine 2 starts sleep
goroutine 1 finished sleep
goroutine 2 finished sleep
goroutine 3 finished sleep
goroutine 4 finished sleep
wait group waitint finished

可以看到wg.Wait()这一句阻塞住了主线程,直到所有的goroutine结束之后,这里才不再阻塞。

  • Once 相对而言是比较简单的,就是控制让某个函数仅仅执行一次,后面无论怎么调用,都不会再执行了。
    示例如下:
package main
import (
 "fmt"
 "sync"
)
func onceFunc(i int) {
 fmt.Printf("goroutine %d run", i)
}
func main() {
 ch := make(chan struct{}, 10)
 var once sync.Once
 for i:=0;i<10;i++ {
 go func() {
 once.Do(func() {
 onceFunc(i)
 })
 ch <- struct{}{}
 }()
 }
 for i:=0;i<10;i++ {
 <- ch
 }
}

输出如下(多线程的原因,输出结果不唯一):

goroutine 3 run

可以看到尽管我们在代码里调用了10次,但是实际上只会执行1次。

  • Cond 实现一个条件变量。代码示例如下:
package main
import (
 "fmt"
 "sync"
 "time"
)
var count int = 4
func main() {
 ch := make(chan struct{}, 5)
 var l sync.Mutex
 cond := sync.NewCond(&l)
 for i:=0;i<5;i++ {
 go func(i int) {
 cond.L.Lock()
 defer func() {
 cond.L.Unlock()
 ch <- struct{}{}
 }()
 for count > i {
 cond.Wait()
 fmt.Printf("收到一个通知 goroutine%d\n", i)
 }
 fmt.Printf("goroutine%d 执行结束\n", i)
 }(i)
 }
 time.Sleep(time.Millisecond * 20)
 fmt.Println("broadcast...")
 cond.L.Lock()
 count -= 1
 cond.Broadcast()
 cond.L.Unlock()
 time.Sleep(time.Second * 1)
 fmt.Println("signal...")
 cond.L.Lock()
 count -= 2
 cond.Signal()
 cond.L.Unlock()
 time.Sleep(time.Second)
 fmt.Println("broadcast...")
 cond.L.Lock()
 count -= 1
 cond.Broadcast()
 cond.L.Unlock()
 for i:=0; i<5;i++{
 <- ch
 }
}

输出:

goroutine4 执行结束
broadcast...
收到一个通知 goroutine0
收到一个通知 goroutine3
goroutine3 执行结束
收到一个通知 goroutine2
收到一个通知 goroutine1
signal...
收到一个通知 goroutine0
broadcast...
收到一个通知 goroutine0
goroutine0 执行结束
收到一个通知 goroutine2
goroutine2 执行结束
收到一个通知 goroutine1
goroutine1 执行结束

可以看到cond主要是通过条件(count > i)和cond.Wait()函数来阻塞goroutine,而在外部修改了count之后,通过外部的Signal和Broadcast函数,通知到Lock了这个cond的goroutine,当条件满足时就不再阻塞了,而如果条件不满足,继续阻塞。

  • Pool主要用于存临时对象,具体细节,且听下回分解。

有疑问加站长微信联系(非本文作者)

本文来自:简书

感谢作者:一条大菜狗HS

查看原文:一文看懂golang的sync包

入群交流(和以上内容无关):加入Go大咖交流群,或添加微信:liuxiaoyan-s 备注:入群;或加QQ群:692541889

关注微信
1054 次点击
暂无回复
添加一条新回复 (您需要 后才能回复 没有账号 ?)
  • 请尽量让自己的回复能够对别人有帮助
  • 支持 Markdown 格式, **粗体**、~~删除线~~、`单行代码`
  • 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet
  • 图片支持拖拽、截图粘贴等方式上传

用户登录

没有账号?注册
(追記) (追記ここまで)

今日阅读排行

    加载中
(追記) (追記ここまで)

一周阅读排行

    加载中

关注我

  • 扫码关注领全套学习资料 关注微信公众号
  • 加入 QQ 群:
    • 192706294(已满)
    • 731990104(已满)
    • 798786647(已满)
    • 729884609(已满)
    • 977810755(已满)
    • 815126783(已满)
    • 812540095(已满)
    • 1006366459(已满)
    • 692541889

  • 关注微信公众号
  • 加入微信群:liuxiaoyan-s,备注入群
  • 也欢迎加入知识星球 Go粉丝们(免费)

给该专栏投稿 写篇新文章

每篇文章有总共有 5 次投稿机会

收入到我管理的专栏 新建专栏