21-chan原理5-close

本文详细解析了Go语言中通道(channel)的关闭过程,包括底层实现、加锁机制、等待goroutine的释放及唤醒流程。强调了通道关闭的不可逆性,避免重复关闭导致的panic。

关闭channel

关闭一个channel的底层实现是 runtime.closechan

close(var_chan_1)

//对应汇编
0x0146 00326 (channel.go:51)    CALL    runtime.closechan(SB)

runtime.closechan

src\runtime\chan.go

  1. 加锁
    channel的关闭过程是加锁的。 发送、读取过程也会加锁。
  2. 释放等待读取数据的goroutine
    将chan上接收队列recvq上的所有goroutine弹出,放在队列glist中。
  3. 释放等待发送数据的goroutine
    将chan上发送队列sendq上的所有goroutine弹出,也放在队列glist中。
  4. 解锁
    释放channel上的锁。
  5. 唤醒等待读取、发送的goroutine
    将glist中的所有goroutine进行唤醒。goready()

注意:
不能重复关闭一个chan,否则会panic。

func closechan(c *hchan) {
	if c == nil {
		panic(plainError("close of nil channel"))
	}

	lock(&c.lock)
	if c.closed != 0 {
		unlock(&c.lock)
		panic(plainError("close of closed channel"))
	}

	if raceenabled {
		callerpc := getcallerpc()
		racewritepc(c.raceaddr(), callerpc, funcPC(closechan))
		racerelease(c.raceaddr())
	}

	c.closed = 1

	var glist gList

	// release all readers
	for {
		sg := c.recvq.dequeue()
		if sg == nil {
			break
		}
		if sg.elem != nil {
			typedmemclr(c.elemtype, sg.elem)
			sg.elem = nil
		}
		if sg.releasetime != 0 {
			sg.releasetime = cputicks()
		}
		gp := sg.g
		gp.param = nil
		if raceenabled {
			raceacquireg(gp, c.raceaddr())
		}
		glist.push(gp)
	}

	// release all writers (they will panic)
	for {
		sg := c.sendq.dequeue()
		if sg == nil {
			break
		}
		sg.elem = nil
		if sg.releasetime != 0 {
			sg.releasetime = cputicks()
		}
		gp := sg.g
		gp.param = nil
		if raceenabled {
			raceacquireg(gp, c.raceaddr())
		}
		glist.push(gp)
	}
	unlock(&c.lock)

	// Ready all Gs now that we've dropped the channel lock.
	for !glist.empty() {
		gp := glist.pop()
		gp.schedlink = 0
		goready(gp, 3)
	}
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值