Go 并发编程 Goroutines,Channels,sync 包
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.RWMutex
,sync.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
}
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
上一篇: Go 函数进阶
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论