Go并发通信 | 青训营笔记

60 阅读2分钟

这是我参与「第五届青训营」伴学笔记创作活动的第10天。

这篇文章将会介绍Go语言中的并发通信相关知识,包括channel的一些进阶用法、waitGroup的用法以及锁的用法。

channel

之前的Go并发基础知识已经大致介绍了channel的基本用法,这里介绍一些常用的channel类型和channel相关的错误和异常。

image.png

无缓冲的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。

image.png

有缓冲的channel不是同步的,发送者和接收者可以异步进行操作,只要channel内的数据没有超过缓冲大小,channel就不会阻塞。这种通道很适合于生产者消费者的情况,生产者可以不断向channel加入信息,而不是只能加入一个信息就要等待消费者取出信息。

另外,我们可以使用内置的len函数获取通道内元素的数量,使用cap函数获取通道的容量,虽然我们很少会这么做。

下面的表格列出了channel使用中可能遇到的异常情况:

image.png

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()
}