Golang channel

1、什么是 channel(管道/通道)?

在 golang 中,channel 是多个 goroutine (线程) 之间传递和同步数据的一种手段。同一时刻,仅有一个线程可以向 channel 发送数据,同样的,同一时刻也只能有一个线程能从 channel 读取数据。

channel 的特性使得它可以解决并发编程可能造成的死锁问题,同时也是 Mutex (上锁)及 sync 多线程编程的替代方案,可以简化代码结构。

2、channel 的声明方式

1
2
3
4
5
var c chan int  // var 关键字声明,缓冲int类型数据
var c chan string // 缓冲string类型数据
c := make(chan int) // make关键字声明,非缓冲管道
c := make(chan int, 10) // 缓冲管道
type ChanInt chan int // 设置别名

总结为:

  • 通过 chan 关键字标识为管道
  • 有两种声明方式
  • make 方式可以生成缓冲管道非缓冲管道

3、缓冲 channel 和无缓冲 channel 区别?

1
2
3
4
channel1 := make(chan int) // 无缓冲
channel2 := make(chan int, 2) // 有缓冲
len(channel1) // 长度
cap(channel2) // 容量

无缓冲:
缓冲为1,最多只能缓冲1个数据。无缓冲的读写实际为同步操作,只有当写与读操作匹配时,才会继续,否则会死锁!所以不能同时将读写置于主线程,如果主线程中有读/写操作,必须置于分线程的声明后,以下为三种正确的写法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
c := make(chan int)

// 1
go func() {
<-
}
c <- 1

// 2
go func() {
c <- 1
}
<-c

// 3
go func() {
c <- 1
}
go func() {
<-c
}
非缓冲的读和写操作必须“相遇”才能执行,否则会**阻塞**。就是一个送信人去你家门口送信 ,你不在家 他不走,你一定要接下信,他才会走

有缓冲:
最大缓冲等于容量,相当于一个先进先出的队列。有缓冲的管道,只有当管道容量已存满,再未取走值的情况下继续放值时才会发生阻塞。

4、巧用 channel

通过 channel 的某些奇技淫巧,可以替代 sync,简化代码量,如下示例:

1、 在一个非缓冲管道中,依次循环存入值,并逐个取出

sync实现方法:

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
package main

import (
"fmt"
"sync"
)

var wg sync.WaitGroup //队列

func main() {

// 先创建一个非缓冲管道
c := make(chan int)
wg.Add(2)
go func() { //子线程
for i := 0; i < 10; i++ {
c <- i //写入数据
fmt.Println("写入数据")
}
wg.Done()
}()

go func() {
for i := 0; i < 10; i++ {
a := <-c
fmt.Println(a) //读取数据
fmt.Println("读取数据")
}
wg.Done()
}()

wg.Wait()

}

=> 使用 channel 实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package main

import "fmt"

func main() {
c := make(chan int)
go func() {
for i:=10; i<10; i++ {
fmt.Println("------")
c <- i
}
close(c)
}()

for n := range c {
fmt.Println("+++++")
fmt.Println(n)
}
}

注:for n := range nclose(c) 配合使用,成对出现。for 循环会监视管道c,一旦管道中有数据,立即执行循环中的命令,取出值,直到 close(c) 命令关闭管道,循环结束

2、 等待多个分线程执行完毕再关闭通道——巧用多个 channel

sync 实现方法:

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
package main

import (
"fmt"
"sync"
)

func main() {

c := make(chan int) //创建通道

var wg sync.WaitGroup //队列
wg.Add(2)
go func() {
fmt.Println("111111111")

for i := 0; i < 10; i++ {
c <- i
}
wg.Done()
}()

go func() {
fmt.Println("22222222")
for i := 10; i < 20; i++ {
c <- i
}
wg.Done()
}()

go func() {
fmt.Println("33333333")
wg.Wait()
close(c)
}()

for n := range c {

// <-c 有返回值
fmt.Println(n)
}
}

=> 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
package main

import (
"fmt"
)

func main() {

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

go func() {
for i := 0; i < 10; i++ {
c <- i
}
done <- true
}()

go func() {
for i := 0; i < 10; i++ {
c <- i
}
done <- true
}()

go func() {
<-done //死锁
<-done
close(c)
}

for n := range c {
fmt.Println(n)
}
}

Author

Ludis

Posted on

2018-01-30

Updated on

2018-02-13

Licensed under

Comments