β

Golang channel进阶

行者无疆 38 阅读

将向管道中写入数据的称为“生产者”,从管道中读取数据的称为“消费者”。

1、生产者与消费者关系

在上篇文章中,生产者与消费者是 1:1 n:1 的关系,那么能不能实现 1:n 的关系嘞?即一个生产者向管道添加数据,多个消费者从管道读取?示例如下:

1:2 案例:

package main

import (  
    "fmt"
)

// 生产者 对 消费者 :1 -> 2
func main() {

    c := make(chan int)
    done := make(chan bool)

    // 生产者:大黄
    go func() {
        for i := 0; i < 100; i++ {
            fmt.Println("生成者 大黄:", i)
            c <- i
        }
        close(c)
    }()

    // 消费者:小明
    go func() {
        // range 会一直不断检测c管道中的数据,如果有,读取,否则等待,直到显示的close关闭通道
        for n := range c {
            fmt.Println("消费者:小明:", n)
        }
        done <- true
    }()

    // 消费者:大明
    go func() {
        // n 等价于 <-c
        for n := range c {
            fmt.Println("消费者:大明:", n)
        }
        done <- true
    }()

    <-done
    <-done
}

修改上述案例,实现 1:n

package main

import (  
    "fmt"
)

// 生产者 对 消费者 :1 -> n
func main() {

    c := make(chan int)
    done := make(chan bool)
    n := 10
    // 生产者:大黄
    go func() {
        for i := 0; i < 100; i++ {
            fmt.Println("生成者生产数据:", i)
            c <- i
        }
        close(c)
    }()

    for i := 0; i < n; i++ {
        // 消费者:小明
        go func(idx int) {
            // range 会一直不断检测c管道中的数据,如果有,读取,否则等待,直到显示的close关闭通道
            for n := range c {
                fmt.Println("消费者",idx,"消费数据:", n)
            }
            done <- true
        }(i)
    }

    for i := 0; i < n; i++ {
        <-done
    }

}

在循环创建消费者分线程时,使用闭包特性,将 i 值保存在分线程中。

2、封装 channel

为了提高代码可读性、复用性,便于维护,可以将 channel 封装于函数方法中, channel 可作为函数的参数或返回值,示例如下:

package main

import "fmt"

// 生产者和消费者一定是配对的状态

func main() {  
    // c是一个管道
    c := incrementor() //把管道作为返回值
    cSum := puller(c)  //把管道作为参数
    for n := range cSum {
        fmt.Println(n) // 0 ...9
    }
}

// 类型:func () chan int
// chan int 返回值类型
func incrementor() chan int {  
    // 创建一个管道
    out := make(chan int)
    // 通过主线程创建一个分线程
    go func() { //子线程
        for i := 0; i < 10; i++ {
            out <- i //生产数据
        }
        close(out)
    }()
    // 返回out管道
    return out
}

// 函数类型:func (chan int) chan int
// 返回值类型:chan int
// 参数类型:chan int
func puller(c chan int) chan int {  
    // 创建一个新的管道
    out := make(chan int)
    // 创建一个子线程
    go func() {
        var sum int

        // <-c
        // 通过range取读取管道c里面的数据,这个for跳出循环的时间为管道c被关闭
        for n := range c {
            sum += n
        }

        // out <- sum 什么时候执行?
        out <- sum //生产者
        close(out)
    }()
    return out
}

3、单向管道、双向管道

双向通道: 前面我们以 var c chan int 形式声明的通道,即类型为 chan type channel 都是双向通道。双向通道顾名思义,就是能存数据又能读数据 单向通道: 单向通道,就是只能存或只能读的 channel 声明单向通道:

var readChan <-chan int // 只读的channel  
var c WriteChan<- int // 只写的channel

readChan <- 1  // 报错  
<-writeChan    // 报错  

对只读的管道执行写操作、对只写的管道执行读操作都会报错。

作者:行者无疆
Welcome to blog
原文地址:Golang channel进阶, 感谢原作者分享。

发表评论