2025年4月9日 星期三 乙巳(蛇)年 正月初十 设为首页 加入收藏
rss
您当前的位置:首页 > 计算机 > 编程开发 > Go语言

Go 并发编程 Goroutines,Channels,sync 包

时间:12-14来源:作者:点击数:6

Goroutines

在 Go 语言中,每一个并发的执行单元叫作一个 goroutine。使用 go 关键字即可以创建一个 goroutine,使得我们能够并发执行一些任务:

  • go func() () {
  • // ...
  • }()

goroutine是并发执行的,不会阻塞下面的操作。但如果我们使用了多个goroutine,并且想要等待这些goroutine全都运行完毕再执行下一步的操作,这时可以使用sync.WaitGroup

  • wg := sync.WaitGroup{}
  • wg.Add(1)
  • go func() () {
  • defer wg.Done()
  • // ...
  • }()
  • wg.Add(1)
  • go func() () {
  • defer wg.Done()
  • // ...
  • }()
  • ...
  • wg.Wait() // 等待上面的goroutine都执行完毕

Channels

一个 channel 是一个通信机制,它可以让一个goroutine通过它给另一个goroutine发送值信息。每个channel都有一个特殊的类型,也就是channels可发送数据的类型。一个可以发送int类型数据的channel一般写为chan int。一个channel有发送和接受两个主要操作

  • ch := make(chan int) // ch has type 'chan int',是一个不带缓存的channel
  • ch <- x // a send statement
  • x = <-ch // a receive expression in an assignment statement
  • <-ch // a receive statement; result is discarded
  • for x := range ch {
  • fmt.Println(x) // channel支持range操作,当channel关闭且没有值可接收时for循环退出
  • }
  • close(ch) // 关闭操作
  • x, ok := <-ch // 对于已经关闭的channel,ok为false

Channel还支持close操作,用于关闭channel,随后对基于该channel的任何发送操作都将导致panic异常。对一个已经被close过的channel进行接收操作依然可以接受到之前已经成功发送的数据;如果channel中已经没有数据的话将产生一个零值的数据。试图重复关闭一个channel将导致panic异常,试图关闭一个nil值的channel也将导致panic异常。

channel分为带缓存的和不带缓存的两种。一个基于无缓存Channels的发送操作将导致发送者goroutine阻塞,直到另一个goroutine在相同的Channels上执行接收操作;反之,如果接收操作先发生,那么接收者goroutine也将阻塞,直到有另一个goroutine在相同的Channels上执行发送操作。

通过ch := make(chan string, 3)的方式可以创建一个带缓存的channel,如果内部缓存队列是满的,那么发送操作将阻塞直到因另一个goroutine执行接收操作而释放了新的队列空间。相反,如果channel是空的,接收操作将阻塞直到有另一个goroutine执行发送操作而向队列插入元素。内置的cap()可以获取channel的容量,而len()可以获取channel中有效元素的个数。

可以通过带缓存的管道来实现最大并发数控制:

  • var limit = make(chan int, 3)
  • func main() {
  • for _, w := range work {
  • go func() {
  • limit <- 1
  • w()
  • <-limit
  • }()
  • }
  • select{}
  • }

select语句会选择case中能够执行的语句去执行,有点类似switch,如果多个case同时就绪时,select会随机地选择一个执行

  • for {
  • select {
  • case <-ch1:
  • // ...
  • case x := <-ch2:
  • // ...use x...
  • case ch3 <- y:
  • // ...
  • case <-time.After(time.Second):
  • // ... timeout
  • default:
  • // ...
  • return
  • }
  • }

需要注意的是,select中如果用了break,跳出的只是select,而不是外面的for循环

sync.Mutex

  • var mu sync.Mutex
  • func DoSomething() {
  • mu.Lock() // 如果已经上锁,则这一步会阻塞直到锁被释放
  • defer mu.Unlock()
  • // ...
  • }

除此之外,还有sync.RWMutexsync.Once也提供了很不错的特性。

sync.RWMutex提供了读写锁,一般有以下几种情况:

  • 在没有写锁的情况下,读锁是无阻塞的
  • 写锁之间互斥,存在写锁,则其他写锁阻塞
  • 写锁和读锁之间互斥,存在写锁则读锁阻塞,存在读锁则写锁阻塞

使用sync.RWMutex加读写锁的方法:

  • Lock 加写锁;Unlock 释放写锁
  • RLock 加读锁;RUnlock 释放读锁

sync.Once一般用于初始化变量,好处是可以在代码任意位置用到的时候再初始化,而不一定要放在init中,并且并发场景下是线程安全的,基于sync.Once重新实现单例模式:

  • var (
  • instance *singleton
  • once sync.Once
  • )
  • func Instance() *singleton {
  • once.Do(func() {
  • instance = &singleton{}
  • })
  • return instance
  • }
方便获取更多学习、工作、生活信息请关注本站微信公众号城东书院 微信服务号城东书院 微信订阅号
推荐内容
相关内容
栏目更新
栏目热门
本栏推荐