Golang channel进阶

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

1、生产者与消费者关系

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

1:2案例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
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可作为函数的参数或返回值,示例如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
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
声明单向通道:

1
2
3
4
5
var readChan <-chan int // 只读的channel
var c WriteChan<- int // 只写的channel

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

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

Author

Ludis

Posted on

2018-02-01

Updated on

2018-02-13

Licensed under

Comments