区块链 Go并发编程之Channel生产者-消费者模型及单向Channel

生产者与消费者关系

在channel中生产者消费者关系可以简单的理解为:

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

先看一个简单的案例,生产者与消费者关系为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

}
输出为
生成者 大黄 0
生成者 大黄 1
生成者 大黄 2
消费者小明: 1
消费者小明: 2
生成者 大黄 3
生成者 大黄 4
消费者小明: 3
消费者小明: 4
生成者 大黄 5
生成者 大黄 6
消费者小明: 5
消费者小明: 6
消费者大明: 0
生成者 大黄 7
生成者 大黄 8
生成者 大黄 9
消费者大明: 8
消费者大明: 9
生成者 大黄 10
生成者 大黄 11
消费者大明: 10
消费者大明: 11
生成者 大黄 12
生成者 大黄 13
消费者大明: 12
消费者大明: 13
生成者 大黄 14
生成者 大黄 15
消费者大明: 14
消费者大明: 15
生成者 大黄 16
生成者 大黄 17
消费者大明: 16
消费者大明: 17
生成者 大黄 18
消费者小明: 7
生成者 大黄 19
生成者 大黄 20
消费者小明: 19
消费者小明: 20
生成者 大黄 21
生成者 大黄 22
消费者小明: 21
消费者小明: 22
消费者大明: 18
生成者 大黄 23
生成者 大黄 24
生成者 大黄 25
消费者大明: 24
消费者大明: 25
生成者 大黄 26
生成者 大黄 27
消费者小明: 23
消费者小明: 27
消费者大明: 26
生成者 大黄 28
生成者 大黄 29
生成者 大黄 30
消费者大明: 29
消费者大明: 30
生成者 大黄 31
生成者 大黄 32
消费者大明: 31
消费者大明: 32
生成者 大黄 33
消费者小明: 28
生成者 大黄 34
生成者 大黄 35
消费者小明: 34
消费者小明: 35
生成者 大黄 36
生成者 大黄 37
消费者小明: 36
消费者小明: 37
生成者 大黄 38
生成者 大黄 39
消费者大明: 33
消费者大明: 39
生成者 大黄 40
生成者 大黄 41
消费者小明: 38
消费者小明: 41
消费者大明: 40
生成者 大黄 42
生成者 大黄 43
生成者 大黄 44
消费者大明: 43
消费者大明: 44
消费者小明: 42
生成者 大黄 45
生成者 大黄 46
生成者 大黄 47
消费者小明: 46
消费者小明: 47
生成者 大黄 48
生成者 大黄 49
消费者大明: 45
消费者大明: 49
生成者 大黄 50
生成者 大黄 51
消费者大明: 50
消费者大明: 51
消费者小明: 48
生成者 大黄 52
生成者 大黄 53
生成者 大黄 54
消费者小明: 53
消费者小明: 54
消费者大明: 52
生成者 大黄 55
生成者 大黄 56
生成者 大黄 57
消费者大明: 56
消费者大明: 57
生成者 大黄 58
生成者 大黄 59
消费者大明: 58
消费者小明: 55
消费者大明: 59
生成者 大黄 60
生成者 大黄 61
生成者 大黄 62
消费者大明: 61
消费者大明: 62
生成者 大黄 63
生成者 大黄 64
消费者大明: 63
消费者大明: 64
消费者小明: 60
生成者 大黄 65
生成者 大黄 66
生成者 大黄 67
消费者小明: 66
消费者小明: 67
生成者 大黄 68
消费者大明: 65
生成者 大黄 69
生成者 大黄 70
消费者大明: 69
消费者大明: 70
生成者 大黄 71
生成者 大黄 72
消费者小明: 68
消费者小明: 72
生成者 大黄 73
生成者 大黄 74
消费者小明: 73
消费者小明: 74
消费者大明: 71
生成者 大黄 75
生成者 大黄 76
生成者 大黄 77
消费者大明: 76
消费者小明: 75
消费者大明: 77
生成者 大黄 78
生成者 大黄 79
生成者 大黄 80
消费者大明: 79
消费者大明: 80
生成者 大黄 81
生成者 大黄 82
消费者大明: 81
消费者大明: 82
消费者小明: 78
生成者 大黄 83
生成者 大黄 84
生成者 大黄 85
消费者小明: 84
消费者小明: 85
生成者 大黄 86
生成者 大黄 87
消费者小明: 86
消费者小明: 87
消费者大明: 83
生成者 大黄 88
生成者 大黄 89
生成者 大黄 90
消费者大明: 89
消费者大明: 90
生成者 大黄 91
生成者 大黄 92
消费者小明: 88
消费者小明: 92
生成者 大黄 93
生成者 大黄 94
消费者大明: 91
消费者大明: 94
生成者 大黄 95
消费者小明: 93
生成者 大黄 96
生成者 大黄 97
消费者小明: 96
消费者小明: 97
消费者大明: 95
生成者 大黄 98
生成者 大黄 99
消费者大明: 99
消费者小明: 98

注:

从打印输出来看

在第一行已经打印出生成者 大黄: 0然而在第十四行才打印出消费者:大明: 0 对于这样得现象可以这么理解:生产者大黄在生产出0后,消费者大明已经接收到0,但还没有来及执行fmt.Println("消费者:大明:", n)这行代码,也就说还没有来及打印,时间片已经切换到生产者,被生产者抢占资源执行了fmt.Println("生成者 大黄:", i) 这行代码,生产出了1。同样的道理消费者小明还没有来及执行打印输出1,时间片已经切换到生产者大黄执行生产2

生产者与消费者为: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(k int) {
			// range 会一直不断检测c管道中的数据,如果有,读取,否则等待,直到显示的close关闭通道
			for n := range c {
				fmt.Printf("消费者%d消费数据:%d\n", k, n)
			}
			done <- true
		}(i)
	}

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

}


注:

1.生产者中的close和消费者中的range是匹配出现的: range会不停的检测c管道中的数据,如果有数据,就读去,否则一直等待,直到显示的close关闭通道,才停止检测c管道中数据

2.在循环中创建消费者的分线程,把i的值保存在分线程中,运用的是闭包的特性

//闭包:
func f(i int) func() int {
    return func() int {
        i++
        return i
    }
}
函数f返回了一个函数返回的这个函数就是一个闭包
这个函数中本身是没有定义变量i的而是引用了它所在的环境函数f中的变量i

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
}

注:

这样写代码简洁明了,封装方法,便于后期维护

out <- sum 什么时候执行?

当range取出incrementor()中的out管道中的每一个值,然后加起来得到sum

单向管道、双向管道

    // chan int 类型
    var chan_test chan int //双向通道
	// <-chan int 类型
	var read_test <-chan int //单向通道 只支持 读
	// chan<- int
	var write_test chan<- int //单向通道 只支持 写
	read_test <- 100          //会报错。因为这个通道 只是单项通道只支持读 不支持写
	<-write_test              //会报错。因为这个通道 只是单项通道只支持写 不支持读

注:

双向通道既能写入数据也可以读取数据,chan int 类型

单项通道:

<-chan int 类型 只能读取数据

chan<- int 类型 只能写入数据

© 版权声明
THE END
喜欢就支持一下吧
点赞0
分享