这是我参与「第五届青训营」伴学笔记创作活动的第10天。
这篇文章将会介绍Go语言中的并发通信相关知识,包括channel的一些进阶用法、waitGroup的用法以及锁的用法。
channel
之前的Go并发基础知识已经大致介绍了channel的基本用法,这里介绍一些常用的channel类型和channel相关的错误和异常。
无缓冲的channel又称为阻塞的channel,因为这种channel没有缓冲,所以channel需要在接收端接收信息后才能继续发送信息,在此之前,channel将会被阻塞住。正是因为这种阻塞的性质,使用无缓冲的channel时需要注意在主协程的channel发送端一定要有对应的接收端,否则会因为没有接收者接收信息而被阻塞在发送信息语句上,形成死锁。比如下面这段代码,它能够顺利通过编译:
func main() {
ch := make(chan int)
ch <- 10
fmt.Println("发送成功")
}
但是在执行时,它会抛出下面的错误提示开发者发生了死锁,这是因为ch没有接收者处理ch <- 10这个发送语句:
fatal error: all goroutines are asleep - deadlock!
goroutine 1 [chan send]:
main.main()
.../src/github.com/pprof/studygo/day06/channel02/main.go:8 +0x54
可见,无缓冲的channel的发送者和接收者是同步的,因此又被称为同步channel。
有缓冲的channel不是同步的,发送者和接收者可以异步进行操作,只要channel内的数据没有超过缓冲大小,channel就不会阻塞。这种通道很适合于生产者消费者的情况,生产者可以不断向channel加入信息,而不是只能加入一个信息就要等待消费者取出信息。
另外,我们可以使用内置的len函数获取通道内元素的数量,使用cap函数获取通道的容量,虽然我们很少会这么做。
下面的表格列出了channel使用中可能遇到的异常情况:
waitGroup
waitGroup位于Sync.waitGroup,通过waitGroup可以优雅地实现并发任务的同步,waitGroup有Add()、Done()和Wait()三个常用的方法,其中,Add方法接收一个int类型的参数,将会使计数器加上对应的值,Done方法会使计数器减一,Wait方法会阻塞到计数器为0。因此,可以使用waitGroup来等待并发任务的执行,例如,使用waitGroup,设置计数器的值为某个任务的协程/线程数,而在协程/线程执行的最后调用Done方法,在需要执行完成该任务才能继续执行的语句之前使用Wait方法,这样就可以实现等待任务执行完成的效果。下面是具体的例子:
var wg sync.WaitGroup
func hello() {
defer wg.Done()
fmt.Println("Hello Goroutine!")
}
func main() {
wg.Add(1)
go hello() // 启动另外一个goroutine去执行hello函数
fmt.Println("main goroutine done!")
wg.Wait()
}