Go语言使用channel进行goroutine通信

声明channel

channel是go语言中的一种数据类型,也叫通道 声明方式

1
ch:=make(chan string, n)
  • ch : channel的变量名
  • chan : 声明channel的关键字
  • string : channel中存储额数据类型
  • n: 缓冲长度(不填时代表无缓冲)

如果给一个 nil 的 channel 发送数据,会造成永远阻塞。
如果从一个 nil 的 channel 中接收数据,也会造成永久阻塞。 给一个已经关闭的 channel 发送数据,会引起 panic
从一个已经关闭的 channel 接收数据,如果缓冲区中为空,则返回一个零 值。 无缓冲的 channel 是同步的,而有缓冲的 channel 是非同步的。

channel的操作

1.发送数据

1
ch <- "你好"

2.接受数据

1
2
str := <-ch
fmt.Println(str) // 你好

3.测试

 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
func main() {
	ch := make(chan string, 3) // 3 是指channel的缓冲长度为3
	i := 0
	go func() {
		for ; i < 10; i++ {
			ch <- strconv.Itoa(i)
			log.Println("向ch中->发送了  ", i, "此时ch的长度为:", len(ch))
		}
	}()
	go func() {
		for {
			time.Sleep(time.Second * 2)
			log.Println("在ch中<-取出了     ", <-ch, "此时ch的长度为:", len(ch))
		}
	}()
	for {
		time.Sleep(time.Second * 3)
		if len(ch) == 0 {
			break
		}
	}
}

输出结果

2022/08/15 11:25:28 向ch中->发送了   0 此时ch的长度为 1
2022/08/15 11:25:28 向ch中->发送了   1 此时ch的长度为 2
2022/08/15 11:25:28 向ch中->发送了   2 此时ch的长度为 3
2022/08/15 11:25:30 在ch中<-取出了      0 此时ch的长度为: 3
2022/08/15 11:25:30 向ch中->发送了   3 此时ch的长度为 3
2022/08/15 11:25:32 向ch中->发送了   4 此时ch的长度为 3
2022/08/15 11:25:32 在ch中<-取出了      1 此时ch的长度为: 3
2022/08/15 11:25:34 在ch中<-取出了      2 此时ch的长度为: 3
2022/08/15 11:25:34 向ch中->发送了   5 此时ch的长度为 3
2022/08/15 11:25:36 在ch中<-取出了      3 此时ch的长度为: 3
2022/08/15 11:25:36 向ch中->发送了   6 此时ch的长度为 3
2022/08/15 11:25:38 在ch中<-取出了      4 此时ch的长度为: 3
2022/08/15 11:25:38 向ch中->发送了   7 此时ch的长度为 3
2022/08/15 11:25:40 在ch中<-取出了      5 此时ch的长度为: 3
2022/08/15 11:25:40 向ch中->发送了   8 此时ch的长度为 3
2022/08/15 11:25:42 在ch中<-取出了      6 此时ch的长度为: 3
2022/08/15 11:25:42 向ch中->发送了   9 此时ch的长度为 3
2022/08/15 11:25:44 在ch中<-取出了      7 此时ch的长度为: 2
2022/08/15 11:25:46 在ch中<-取出了      8 此时ch的长度为: 1
2022/08/15 11:25:48 在ch中<-取出了      9 此时ch的长度为: 0

4.结果解析

  • 在第一个匿名函数中向channel中发送数据 由于ch的缓冲长度只有3 所以在 11:25:28 这一秒内连续发送了3个字符串进去后ch就开始阻塞

  • 2秒后第二个匿名函数的Sleep结束开始在ch中取数据,取出一个数据后ch不再阻塞,有一个空闲位置,然后第一个匿名函数再往ch中写入一个数据就再次进入阻塞状态

  • 就这样每2秒就取出一个数据然后再写入数据直到11:25:42 此时第一个匿名函数的for循环已经结束,第一个匿名函数已经执行完成了,第二个匿名函数仍在每2秒读取数据,此时ch的长度就开始减少了直到最后长度为0 程序退出

select的使用

假设要从网上下载一个文件,我启动了 3 个 goroutine 进行下载,并把结果发送到 3 个 channel 中。其中,哪个先下载好,就会使用哪个 channel 的结果。这样我怎么知道才能知道那个channel中线有数据呢?通过下面的代码架构实现

1
2
3
4
5
6
7
8
select {
case i1 = <-c1:
     //todo
case i2 <- c2:
	//todo
default:
	// default todo
}

下面实现刚才的模拟下载的代码

 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
func downloadFile(chanName string) string {
	//模拟下载文件,可以自己随机time.Sleep点时间试试
	rand.Seed(time.Now().Unix())
	time.Sleep(time.Second * time.Duration(rand.Intn(5)))
	return chanName + ":filePath"
}
func main() {
	firstCh := make(chan string)
	secondCh := make(chan string)
	threeCh := make(chan string)
	//同时开启3个goroutine下载
	go func() {
		firstCh <- downloadFile("firstCh")
	}()
	go func() {
		secondCh <- downloadFile("secondCh")
	}()
	go func() {
		threeCh <- downloadFile("threeCh")
	}()
	//开始select多路复用,哪个channel能获取到值,
	var finalPath string
	//就说明哪个最先下载好,就用哪个。
	select {
	case filePath := <-firstCh:
		finalPath = filePath
	case filePath := <-secondCh:
		finalPath = filePath
	case filePath := <-threeCh:
		finalPath = filePath
	}
	fmt.Println(finalPath)
}