美文网首页田字簿
Go语言channel备忘录

Go语言channel备忘录

作者: 就想叫yoko | 来源:发表于2019-07-17 14:28 被阅读0次

    目录

    1. 无缓冲channel等价于缓冲大小为0的channel,而不是1
    2. 发送者和接收者哪些情况会阻塞
    3. close哪些情况会导致panic
    4. 如何优雅的关闭channel
    5. 当一个select中有多个channel满足可读时,谁被激活
    6. select with default
    7. 读取时获取第二个返回值,以此判断该channel是否被关闭
    8. close前写入的数据,接收者依然可以按顺序读取到
    9. 一个channel有多个接收者时,close channel会唤醒所有接收者
    10. 配合timer实现channel读取的超时机制
    11. 当channel只用做同步通知,不关心channel中传输的值时,可使用 chan struct{} 类型
    12. 单向channel类型的作用
    13. 可以make单向channel,但是这样做没有意义
    14. channel配合for range的简化写法
    1. 无缓冲channel等价于缓冲大小为0的channel,而不是1
    var ch chan int // ch == nil
    
    // 创建无缓冲channel
    ch := make(chan int) // ch != nil
    // 等价于
    // ch := make(chan int, 0)
    // 不等价于
    // ch := make(chan int, 1)
    
    close(ch) // close执行后, ch != nil
    
    2. 发送者和接收者哪些情况会阻塞
    • 往值为nil的channel发送数据: 永久阻塞
    • 从值为nil的channel读取数据: 永久阻塞
    • 无缓冲模式的发送者: 阻塞直到数据被接收者接收
    • 无缓冲模式的接收者: 无数据可读时,阻塞
    • 有缓冲模式的发送者: 当缓冲满时,阻塞
    • 有缓冲模式的接收者: 无数据可读时,阻塞
    3. close哪些情况会导致panic
    1. close值为nil的channel
    2. close已经被close的channel
    3. 向已经被close的channel发送数据
    4. 如何优雅的关闭channel

    需要特别注意:

    1. 接收者关闭channel要小心,因为关闭后发送者继续发送会panic
    2. 当有多个发送者时,在一个发送者协程中关闭channel要小心,因为关闭后其他发送者继续发送会panic

    复杂情况下的参考思路:

    1. channel的关闭并非必须的,只要channel对象不再被持有,垃圾回收器会清理它
    2. 可使用原子变量等同步原语保证close有且只有发生一次
    3. 除了传输数据的channel,可以再增加channel配合select使用,用于取消生产、消费
    4. 接收端也可以通过其他channel发出消息,反向通知发送端
    5. 当一个select中有多个channel满足可读时,谁被激活

    Go随机选取一个满足条件的case分支执行,而不是按代码顺序选取。

    // 如下代码段,可能输出ch1,也可能输出ch2
    ch1 := make(chan int, 8)
    ch2 := make(chan int, 8)
    ch1 <- 1
    ch2 <- 1
    select {
    case <- ch1:
        fmt.Println("ch1")
    case <- ch2:
        fmt.Println("ch2")
    }
    
    6. select with default

    当select中的条件都不满足时,会立即执行default分支

    // 如下代码段,会立即打印default
    ch1 := make(chan int, 1)
    ch2 := make(chan int)
    select {
    case <- ch1:
        fmt.Println("ch1")
    case <- ch2:
        fmt.Println("ch2")
    default:
        fmt.Println("default")
    }
    
    7. 读取时获取第二个返回值,以此判断该channel是否被关闭
    v, ok := <- ch
    // 当channel被关闭后,v为channel类型的零值,ok为false
    
    8. close前写入的数据,接收者依然可以按顺序读取到
    ch := make(chan int, 8)
    ch <- 1
    ch <- 2
    ch <- 3
    close(ch)
    
    for {
        v, ok := <- ch
        fmt.Println(v, ok)
        if !ok {
            break
        }
    }
    // 以上代码段,将打印出如下结果:
    // 1 true
    // 2 true
    // 3 true
    // 0 false
    
    9. 一个channel有多个接收者时,close channel会唤醒所有接收者
    10. 配合timer实现channel读取的超时机制

    但在高性能场景需要注意,参见: golang源码阅读之定时器以及避坑指南

    11. 当channel只用做同步通知,不关心channel中传输的值时,可使用 chan struct{} 类型

    好处是语意上更正确,代码可读性更高。

    // 初始化
    ch := make(chan struct{}, 1)
    
    // 写入
    ch <- chan struct{}{}
    
    12. 单向channel类型的作用
    // 比如Go系统库中Timer的实现,就使用了只读类型的channel,
    // 目的是限制该channel在上层Timer对象只能读,不能写。这种限制是编译期的
    
    // 提供给用户的Timer对象
    type Timer struct {
        C <-chan Time // 只读类型
        r runtimeTimer
    }
    
    // 实际make的是chan Time类型
    func NewTimer(d Duration) *Timer {
        c := make(chan Time, 1)
        t := &Timer{
            C: c, // chan Time转换成只读类型,后续通过Timer对象访问C数据成员的操作只能读,不能写
            r: runtimeTimer{
                when: when(d),
                f:    sendTime,
                arg:  c, // 将chan Time类型传递给底层runtimeTimer中使用,底层可以写
            },
        }
        startTimer(&t.r)
        return t
    }
    
    // 用户调用After时,返回只读类型的channel
    func After(d Duration) <-chan Time {
        return NewTimer(d).C
    }
    
    13. 可以make单向channel,但是这样做没有意义
    // 以下初始化了一个只写的channel是合法的,但是只能写,不能读,应该没有这种使用场景
    ch := make(chan<- int, 4)
    
    14. channel配合for range的简化写法
    ch := make(chan int, 4)
    ch <- 1
    ch <- 2
    close(ch)
    for v := range ch {
        fmt.Println(v)
    }
    fmt.Println("< for")
    // 以上代码段将打印如下结果
    // 1
    // 2
    // < for
    

    参考链接

    备忘录类型文章,后续的修改会第一时间在我的个人站点更新,地址: https://pengrl.com/p/23102

    相关文章

      网友评论

        本文标题:Go语言channel备忘录

        本文链接:https://www.haomeiwen.com/subject/zqvqlctx.html