关于Golang的nil channel(一个莫名其妙的东西,但如果没有make初始化就会遇到)

本文探讨了Go语言中nil通道的初始化、错误处理以及其在并发编程中的特殊用途,如动态控制select语句、同步goroutine执行和资源管理。作者强调了初始化通道的重要性以避免deadlock问题。
摘要由CSDN通过智能技术生成

前言

众所周知,channel在使用之前需要通过make去进行初始化。否则会是一个nil的channel。在阅读源码中碰到了很多对其的处理步骤,所以学习了一下这边。

正常使用会遇到的错误情况


func main() {
	var c1 chan int
	//c1 := make(chan int, 0)
	var wg sync.WaitGroup
	wg.Add(2)
	go func() {
		c1 <- 1
		wg.Done()
	}()
	go func() {
		tmp := <-c1
		fmt.Println(tmp)
		wg.Done()
	}()
	wg.Wait()
}

这是自己的一段测试代码,运行后会出现——fatal error: all goroutines are asleep - deadlock!

在这里插入图片描述

但如果取消注释的那行,即可以正常运行。

源码情况

	if c == nil {
		if !block {
			return false
		}
		gopark(nil, nil, waitReasonChanSendNilChan, traceBlockForever, 2)
		throw("unreachable")
	}

可以看到,会进入gopark函数,其中


// Puts the current goroutine into a waiting state and calls unlockf on the
// system stack.
//
// If unlockf returns false, the goroutine is resumed.
//
// unlockf must not access this G's stack, as it may be moved between
// the call to gopark and the call to unlockf.
//
// Note that because unlockf is called after putting the G into a waiting
// state, the G may have already been readied by the time unlockf is called
// unless there is external synchronization preventing the G from being
// readied. If unlockf returns false, it must guarantee that the G cannot be
// externally readied.
//
// Reason explains why the goroutine has been parked. It is displayed in stack
// traces and heap dumps. Reasons should be unique and descriptive. Do not
// re-use reasons, add new ones.
func gopark(unlockf func(*g, unsafe.Pointer) bool, lock unsafe.Pointer, reason waitReason, traceReason traceBlockReason, traceskip int) {
	if reason != waitReasonSleep {
		checkTimeouts() // timeouts may expire while two goroutines keep the scheduler busy
	}
	mp := acquirem()
	gp := mp.curg
	status := readgstatus(gp)
	if status != _Grunning && status != _Gscanrunning {
		throw("gopark: bad g status")
	}
	mp.waitlock = lock
	mp.waitunlockf = unlockf
	gp.waitreason = reason
	mp.waitTraceBlockReason = traceReason
	mp.waitTraceSkip = traceskip
	releasem(mp)
	// can't do anything that might move the G between Ms here.
	mcall(park_m)
}

即可以看到,goroutine被休眠了,需要等待唤醒。但是如何唤醒…我的确不知道了。从GPT的解答也没有看出来该怎么做,算是调度器的事了可能。

在这里插入图片描述

使用场景

这部分主要是参考GPT给我的答案,但我说实话没太看懂他是做什么的…所以干脆复制到这里,期待评论区大佬补充。

以下内容由GPT 4.0-0125生成

nil通道在Go语言中虽然在大多数情况下会导致阻塞,但它们在特定的场景下可以被用作有用的同步工具或状态控制机制。下面是一些使用nil通道的场景:

  1. 动态控制select语句的case执行
    在select语句中,对nil通道的操作(不论是发送还是接收)都不会被选中执行。这个特性可以用来动态地启用或禁用select语句的某些分支。通过将通道设置为nil或一个实际的通道,你可以控制哪些操作可以执行,这样可以基于程序的当前状态动态地改变程序的行为。
var c chan int // c是一个nil通道

go func() {
    select {
    case c <- 1:
        // 这个case不会执行,因为c是nil
    default:
        fmt.Println("c is nil")
    }
}()

// 根据条件动态地使某个case可执行或不可执行
if condition {
    c = make(chan int, 1) // 初始化c,使得上面的case可执行
}
  1. 防止goroutine无谓的运行
    有时候你可能希望基于某些条件阻止goroutine的进一步执行,直到这个条件改变。使用nil通道可以达到这个目的,因为从nil通道的接收或向nil通道的发送都会阻塞,你可以利用这一点来控制goroutine的执行流程。

  2. 资源清理和管理
    在一些需要根据资源是否可用来控制并发操作的场景中,nil通道可以作为一种表示资源状态的机制。比如,当某个资源不可用时,相关的通道可以被设置为nil,相应的操作因为等待在nil通道上而阻塞,直到资源再次变得可用时,通道被重新初始化,阻塞的操作得以继续。

  3. 简化代码逻辑
    在某些情况下,使用nil通道可以简化代码逻辑。例如,在使用select处理多个通道时,如果某个通道暂时不需要被处理,将其设置为nil可以避免引入额外的逻辑来判断是否需要处理该通道。

总结
虽然nil通道的使用场景比较特殊,但它们在控制并发程序的行为、管理资源状态等方面提供了一种简洁而强大的机制。正确使用nil通道可以使得并发程序的设计更加灵活和清晰。

总结

我的确不太明白这个东西有啥用,但给我的警示就是:一定记得初始化。

  • 15
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值