Ezreal 书架 Ezreal 书架
Home
  • 《Go程序员面试笔试宝典》
  • 《RabbitMQ 实战指南》
  • 《深入理解kafka》
  • MySQL45讲
  • 透视HTTP协议
  • 结构化数据的分布式存储系统
  • Raft 共识算法
Home
  • 《Go程序员面试笔试宝典》
  • 《RabbitMQ 实战指南》
  • 《深入理解kafka》
  • MySQL45讲
  • 透视HTTP协议
  • 结构化数据的分布式存储系统
  • Raft 共识算法
  • 逃逸分析

  • 延迟语句

  • 数据容器

  • 通道

    • CSP是什么
    • 通道有哪些应用
    • 通道的底结构
    • 通道的关闭过程发生了什么
    • 从一个关闭的通道里仍然能读出数据吗
    • 如何优雅地关闭通道
    • 关于通道的 happens-before 有哪些
    • 通道在什么情况下会引起资源泄漏
    • 通道操作的情况总结
  • 接口

  • unsafe

  • context

  • Go程序员面试笔试宝典
  • 通道
ezreal_rao
2023-05-06

通道的关闭过程发生了什么

关闭某个 channel,会执行函数 closechan:

// src/runtime/chan.go

func closechan(c *hchan) {
    if c == nil { // 关闭一个 nil channel,panic
        panic(plainError("close of nil channel"))
    }
    lock(&c.lock) // 上锁
    if c.closed != 0 { // 如果 channel 已经关闭
        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 // 修改关闭状态,指示 channel 已被关闭
    var glist gList
    // 将 channel 所有等待接收队列的里 sudog 释放
    for {
        sg := c.recvq.dequeue() // 从接收队列里出队一个 sudog
        if sg == nil { // 出队完毕,跳出循环
            break
        }
        // 如果 elem 不为空,说明此 receiver 未忽略接收数据
        // 给它赋一个相应类型的零值
        if sg.elem != nil {
            typedmemclr(c.elemtype, sg.elem)
            sg.elem = nil
        }
        if sg.releasetime != 0 {
            sg.releasetime = cputicks()
        }
        // 取出 goroutine
        gp := sg.g
        gp.param = nil
        if raceenabled {
            raceacquireg(gp, c.raceaddr())
        }
        // 相连,形成链表
        glist.push(gp)
    }
    // 将 channel 等待发送队列里的 sudog 释放
    // 如果存在,这些 goroutine 将会 panic
    for {
        // 从发送队列里出队一个 sudog
        sg := c.sendq.dequeue()
        if sg == nil {
            break
        }
        // 发送者会 panic
        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) // 解锁
    // 遍历链表
    for !glist.empty() {
        gp := glist.pop() // 取最后一个
        gp.schedlink = 0
        goready(gp, 3) // 唤醒相应 goroutine
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70

关闭 channel 的逻辑相对比较简单,对于一个 channel,recvq 和 sendq 中分别保存了阻塞的发送者和接收者。关闭 channel 后,对于等待接收者而言,会收到一个相应类型的零值;对于等待发送者,会直接 panic。所以,在不清楚 channel 还有没有接收者的情况下,不能贸然关闭它。

函数 closechan () 先上了一把大锁,接着把所有挂在这个 channel 上的 sender 和 receiver 全都连成一个 sudog 链表,再解锁。最后,再将所有的 sudog 全都唤醒。

唤醒之后,sender 会继续执行 chansend 函数里 goparkunlock 函数之后的代码,很不幸,检测到 channel 已经关闭了,发生 panic。而 receiver 则比较幸运,在进行一些扫尾工作后,函数返回。这里,selected 返回 true,而返回值 received 则要根据 channel 是否关闭,返回不同的值。如果 channel 关闭,received 的值为 false,否则为 true。

#通道#csp#channel
上次更新: 5/9/2023, 10:58:32 AM
通道的底结构
从一个关闭的通道里仍然能读出数据吗

← 通道的底结构 从一个关闭的通道里仍然能读出数据吗→

最近更新
01
为什么我的MySQL会抖一下
07-15
02
HTTP 性能优化面面观
07-12
03
WebSocket:沙盒里的 TCP
07-12
更多文章>
Theme by Vdoing | Copyright © 2022-2024 Ezreal Rao | CC BY-NC-SA 4.0
豫ICP备2023001810号
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式