十. go 高性能编程之 其它场景何退出协程 goroutine

文章通过示例展示了在Go语言中,如果不关闭channel可能导致协程无法正常退出的问题。提出了通过检查channel是否关闭来避免协程阻塞的方法,并介绍了使用sync.Once保证channel只关闭一次的优雅方式。此外,文章还阐述了不同场景下channel关闭的原则,包括单发送者与多个接收者、单接收者与多个发送者以及多个发送者和接收者的情况。
摘要由CSDN通过智能技术生成

一. channel 忘记关闭时问题演示

  1. 如下代码: 在do函数中,通过for + select 的模式,等待信道 taskCh 传递任务,并执行
func do(taskCh chan int) {
	for {
		select {
		case t := <-taskCh:
			time.Sleep(time.Millisecond)
			fmt.Printf("task %d is done\n", t)
		}
	}
}

func sendTasks() {
	taskCh := make(chan int, 10)
	go do(taskCh)
	for i := 0; i < 1000; i++ {
		taskCh <- i
	}
}

//运行测试
func TestDo(t *testing.T) {
    t.Log(runtime.NumGoroutine())
    sendTasks()
	time.Sleep(time.Second)
	t.Log(runtime.NumGoroutine())
}
  1. 执行单元测试后,会发现子协程多了一个,有一个协程一直没有得到释放: sendTasks 中启动了一个子协程 go do(taskCh),因为这个协程一直处于阻塞状态,等待接收任务,因此直到程序结束,协程也没有释放
  2. 如果任务全部发送成功,我们如何通知该协程结束等待,正常退出呢?

二. 如何通知协程结束等待正常退出

  1. 先复习一个channel的几种状态:
    在这里插入图片描述
  2. 解决
  1. doCheckClose()函数中,增加"beforeClosed := <-taskCh" 判断 channel 是否已经关闭,beforeClosed 为 false 表示信道已被关闭。若关闭,则不再阻塞等待,直接返回,对应的协程随之退出。
  2. sendTasks 函数中,任务发送结束之后,使用 close(taskCh) 将 channel taskCh 关闭
  3. 后续启动的协程已经正常退出,该协程以及使用到的信道 taskCh 将被垃圾回收,资源得到释放
func doCheckClose(taskCh chan int) {
	for {
		select {
		case t, beforeClosed := <-taskCh:
			if !beforeClosed {
				fmt.Println("taskCh has been closed")
				return
			}
			time.Sleep(time.Millisecond)
			fmt.Printf("task %d is done\n", t)
		}
	}
}

func sendTasksCheckClose() {
	taskCh := make(chan int, 10)
	go doCheckClose(taskCh)
	for i := 0; i < 1000; i++ {
		taskCh <- i
	}
	close(taskCh)
}

//运行测试
func TestDoCheckClose(t *testing.T) {
	t.Log(runtime.NumGoroutine())
	sendTasksCheckClose()
	time.Sleep(time.Second)
	runtime.GC()
	t.Log(runtime.NumGoroutine())
}

三. 通道关闭原则

  1. 不推荐的直接关闭,如下:
func SafeClose(ch chan T) (justClosed bool) {
	defer func() {
		if recover() != nil {
			// 一个函数的返回结果可以在defer调用中修改。
			justClosed = false
		}
	}()

	// 假设ch != nil。
	close(ch)   // 如果 ch 已关闭,将 panic
	return true // <=> justClosed = true; return
}
  1. 使用 sync.Once 或互斥锁(sync.Mutex)确保 channel 只被关闭一次
type MyChannel struct {
	C    chan T
	once sync.Once
}

func NewMyChannel() *MyChannel {
	return &MyChannel{C: make(chan T)}
}

func (mc *MyChannel) SafeClose() {
	mc.once.Do(func() {
		close(mc.C)
	})
}
  1. 优雅的方式:(参考channel文档)

情形一:M个接收者和一个发送者,发送者通过关闭用来传输数据的通道来传递发送结束信号。
情形二:一个接收者和N个发送者,此唯一接收者通过关闭一个额外的信号通道来通知发送者不要再发送数据了。
情形三:M个接收者和N个发送者,它们中的任何协程都可以让一个中间调解协程帮忙发出停止数据传送的信号。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值