go channel 使用及机制流程汇总

协程间通讯的普通使用, 发送值给 channel , 外部 channel 接收.

func t1() {
    ch := make(chan int)
    go func() {
        ch <- 1
    }()

    // <-ch // 导致下面语句阻塞
    fmt.Println("channel int", <-ch)
}
复制代码
channel int 1
复制代码

channel 支持缓冲区

func t2() {
    ch := make(chan int, 3)
    go func() {
        ch <- 1
        ch <- 2
        ch <- 3
        ch <- 4 // 会阻塞写不入, 除非ch已接收
        close(ch) // 关闭后不能再写入, 并且缓冲区内为空时则返回零值
    }()
    fmt.Println("buffer channel int",
        <-ch,
        <-ch,
        <-ch,
        <-ch,
        <-ch)
}
复制代码
buffer channel int 1 2 3 4 0
复制代码

select 是专门给管道定制

func t3() {
    ch1 := make(chan int)
    ch2 := make(chan struct{})

    go func() {
        ch1 <- 2
    }()

    select {
    case <-ch1:
        fmt.Println("select here is ch1")
    case <-ch2:
        fmt.Println("select here is ch2")
    }
}
复制代码
select here is ch1
复制代码

使用 for

func t4() {
    ch := make(chan int, 5)

    go func() {
        for i:=1; i<=5; i++ {
            time.Sleep(time.Millisecond * 10)
            ch <- i
        }
    }()

    for v := range ch { // 会阻塞
        fmt.Println("for channel ", v)
        if v == 5 {
            break
        }
    }
}
复制代码
for channel  1
for channel  2
for channel  3
for channel  4
for channel  5
复制代码

同时使用 selectfor

func t5() {
    chPrint := make(chan struct{}) 
    chStop := make(chan struct{}) 

    go func(){
        time.Sleep(time.Second * 1)
        chPrint <- struct{}{}

        time.Sleep(time.Second * 1)
        chPrint <- struct{}{}

        time.Sleep(time.Second * 1)
        chStop <- struct{}{}
    }()

    var sum int
    for {
        time.Sleep(time.Millisecond)

        select {
        case <-chPrint:
            fmt.Println("for+select now is", sum)
        case <-chStop:
            fmt.Println("for+select stop, result is", sum)
            return
        default:
            if sum == 10000 {
                fmt.Println("for+select end, result is", sum)
                return
            }
            sum += 1
        }
    }
}
复制代码
for+select now is 766
for+select now is 1540
for+select stop, result is 2309
复制代码

判断管道是否关闭

func t6() {
    ch := make(chan struct{})

    go func() {
        close(ch)
        //ch <- struct{}{} // 只运行这句, 输出OK
    }()
    if _, ok := <-ch; ok {
        fmt.Println("if channel is ok")
        return
    }
    fmt.Println("if channel is bad")
}
复制代码
if channel is bad
复制代码

以只发送或只接收为传递参数, 同理也可以为返回值

func t7() {
    ch := make(chan struct{})
    chExit := make(chan struct{})

    go func(chRecv <-chan struct{}) {
        fmt.Println("recv channel")
        <-chRecv
        chExit<- struct{}{}
    }(ch)

    go func(chSend chan<- struct{}) {
        fmt.Println("send channel")
        chSend <- struct{}{}
    }(ch)

    <-chExit
}
复制代码
send channel
recv channel
复制代码

channel机制流程汇总

makechan() 初始化 hchan 结构体, 如果没有缓冲区即分配 hchanSize 大小的内存并返回;而有缓冲区的情况下, 则计算管道元素类型大小并分配 hchanSize +( elem.size * size )大小的内存(缓冲区是一个环形的结构设计), 最后返回 hchan .

chansend()channel 发送数据, 首先锁住当前协程, 有如下几种情况, 按顺序判断:

  1. 有正在等待的接收者, 就立即转发给该接收者, 释放锁并退出.
  2. 有可用的缓冲区就将该值移到目标缓冲区等待被接收, 释放锁并退出.
  3. 是非阻塞(用于select)就退出, 释放锁并退出.
  4. 阻塞当前协程, 并将当前协程放到发送等待队列中并释放锁, 唤醒后清理sudog.

chanrecv() 接收 channel 的数据, 首先锁住当前协程, 有如下几种情况, 按顺序判断:

  1. channel 关闭并没有缓冲数据, 接收者接收的值将会是零值, 释放锁并退出.
  2. 发送队列有发送者, 就立即接收该数据, 释放锁并退出.
  3. 有缓冲数据就将该数据复制给接收者, 释放锁并退出.
  4. 是非阻塞(用于select)就退出, 释放锁并退出.
  5. 阻塞当前协程, 并将当前协程放到发送等待队列中并释放锁, 唤醒后清理sudog. 跟 chansend 差不多, 多了个已关闭并没有缓冲数据的判断.

closechan 关闭 channel , 首先也要获取锁, 关闭管道并释放所有接收和发送的队列并清醒所有sudog. 但缓冲区的数据不会清理, 随时等待被接收.