清水泥沙

golang基础(46.通道错误和异常处理)

在并发编程的通信过程中,最需要处理的就是超时问题:比如向通道发送数据时发现通道已满,或者从通道接收数据时发现通道为空。如果不正确处理这些情况,很可能会导致整个协程阻塞并产生死锁。此外,如果我们试图向一个已经关闭的通道发送数据或关闭已经关闭的通道,也会引发 panic。以上都是我们在使用通道进行并发通信时需要尤其注意的。

超时处理机制实现

Go 语言没有提供直接的超时处理机制,但我们可以借助 select 语句来实现类似机制解决超时问题,因为 select 语句的特点是只要其中一个 case 对应的通道操作已经完成,程序就会继续往下执行,而不会考虑其他 case 的情况。基于此特性,我们来为通道操作实现超时处理机制。

package main

import (
	"fmt"
	"time"
)

func main() {
	// 1.创建生产管道
	ch := make(chan int, 10)

	// 2.创建超时管道
	timeout := make(chan bool)

	// 3.开启超时携程
	go func() {
		time.Sleep(time.Minute)
		timeout <- true
	}()

	// 4.使用select结束阻塞
	select {
	case <-ch:
		fmt.Println("接收到数据")
	case <-timeout:
		fmt.Println("执行超时!")
	}
}

使用 select 语句可以避免永久等待的问题,因为程序会在从 timeout 通道中接收到数据后继续执行,无论对 ch 的读取是否还处于等待状态,从而实现 1 秒超时的效果。这种写法看起来是一个编程小技巧,但却是在 Go 语言并发编程中避免通道通信超时的最有效方法。

避免对已关闭通道进行操作

为了避免对已关闭通道再度执行关闭操作引发 panic,一般我们约定只能在发送方关闭通道,而在接收方,我们则通过通道接收操作返回的第二个参数是否为 false 判定通道是否已经关闭,如果已经关闭,则不再执行发送操作。

package main

import "fmt"

func main() {
	ch := make(chan int, 2)

	go func() {
		for i := 0; i < 10; i++ {
			ch <- i
			fmt.Printf("发送%d 
", i)
		}
		close(ch)
	}()

	for {
		num, ok := <-ch
		if ok {
			fmt.Printf("接收%d 
", num)
		} else {
			fmt.Println("结束")
			break
		}
	}
}