Golang channel进阶

2018-02-01 04:16:18

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

1、生产者与消费者关系

在上篇文章中,生产者与消费者是1:1n: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 typechannel都是双向通道。双向通道顾名思义,就是能存数据又能读数据 单向通道: 单向通道,就是只能存或只能读的channel 声明单向通道:

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

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

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

Golang channel

1、什么是 channel(管道/通道)? 在 golang 中,channel 是多个 goroutine (线程) 之间传递和同步数据的一种手段。同一时刻,仅有一个线程可以向 channel 发送数据,同样的,同一时刻也只能有一个线程能从 channel 读取数据。 channel 的特性使得它可以解决并发编程可能造成的死锁问题,同时也是 Mutex (上锁)及 sync 多线程编程的替代方案,可以简化代码结构。 2、channel 的声明方式 var c chan int // var 关键字声明,缓冲int类型数据 var c chan string // 缓冲string类型数据 c := make(chan int) // make关键字声明,

区块链学习 - solidity合约部署

区块链原理 通过https://anders.com/blockchain/hash.html可以直观的了解hash、block(区块)、blockchain(区块链)、distributed(分布)、tokens(代币)各个概念。 开发环境配置 1、 MetaMask钱包 1.MetaMask是什么? MetaMask是一款在谷歌浏览器Chrome上使用的插件类型的以太坊钱包,该钱包不需要下载,只需要在谷歌浏览器添加对应的扩展程序即可,非常轻量级,使用起来也非常方便。 2.MetaMask安装 chrome浏览器安装MetaMask,直接用MetaMask创建钱包或者通过myetherwallet.com创建钱包,然后用MetaMask导入钱包私钥即可。 开发测试时,最好新建一个测试钱包,万一自己的币搞没就裂了~~ 3.MetaMask常用功能 如上图所示,第一个为正式网络,也就是ETH的公链,真实的币就存在公链上。后两个是两个测试网络,点击后便会切换到测试网络,钱包的地址也会随之改变。为了测试方便,在测试网络的钱包中免费获取一些测试用的代币。 4.测试代币获取 在Ropsten Test