并发编程-通道使用

目录
  1. 1. 1.什么是Channel?
  2. 2. 2. 创建Channel
    1. 2.1. 2.1 语法
    2. 2.2. 2.2 使用示例
  3. 3. 3.发送数据
    1. 3.1. 3.1 语法
    2. 3.2. 3.2 错误使用示例
    3. 3.3. 3.3 正确使用示例
  4. 4. 4.普通接收
    1. 4.1. 4.1 阻塞接收语法
    2. 4.2. 4.2 忽略接收语法
  5. 5. 5. 循环接收
    1. 5.1. 5.1 使用普通for接收
    2. 5.2. 5.2 使用for…range接收
  6. 6. 6. Channle的阻塞特性
    1. 6.1. 6.1 特性如下
    2. 6.2. 6.2 特性使用
  7. 7. 7.关闭Channel
    1. 7.1. 7.1 使用示例
    2. 7.2. 7.2 向已关闭的chan写入数据,会崩溃
    3. 7.3. 7.3 重复关闭chan,会崩溃
  8. 8. 8.缓冲Channel
    1. 8.1. 8.1 语法
    2. 8.2. 8.2 使用示例
  9. 9. 9.单向Channel
    1. 9.1. 9.1 介绍
    2. 9.2. 9.1 使用
  10. 10. 10.计时器与channel
    1. 10.1. 10.1 NewTimer
    2. 10.2. 10.2 After

1.什么是Channel?

channelGo的通道,是协程之间的通信机制。一个channel是一条通信管道,它可以让一个协程通过它给另一个协程发送数据。每个channel都需要指定数据类型,即channel可发送数据的类型。Go语言主张通过数据传递来实现共享内存,而不是通过共享内存来实现数据传递。

2. 创建Channel

2.1 语法

channel是引用类型,需要使用make()进行创建。

1
2
3
4
5
6
// 声明方式1
var cha1 chan 数据类型
cha1 = make(chan 数据类型)

// 声明方式2
cha1 := make(chan 数据类型)

2.2 使用示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
package main
import (
"fmt"
)
type People struct {}
func main() {
// 创建一个整数型chan
intChan := make(chan int)
fmt.Printf("intChan类型: %T 值: %v \n",intChan,intChan)
// 创建一个空接口chan,可以存放任意类型数据
interfaceChan := make(chan interface{})
fmt.Printf("interfaceChan类型: %T 值: %v \n",interfaceChan,interfaceChan)
// 创建一个指针chan
peopleChan := make(chan *People)
fmt.Printf("peopleChan类型: %T 值: %v \n",peopleChan,peopleChan)
}
/** 输出
intChan类型: chan int 值: 0xc000052060
interfaceChan类型: chan interface {} 值: 0xc0000520c0
peopleChan类型: chan *main.People 值: 0xc000052120
*/

3.发送数据

3.1 语法

通过channel发送数据需要使用特殊的操作符<-,需要注意的是: channel发送的值的类型必须与channel的元素类型一致。

1
channel变量名 <- 值

3.2 错误使用示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
package main
import (
"fmt"
"time"
)
type People struct {
}
func main() {
// 创建一个整数型chan
intChan := make(chan int)
// 写入
intChan <- 5
fmt.Printf("intChan类型: %T 值: %v \n", intChan, intChan)
}

上面示例运行会死锁,报错内容如下:

1
2
3
4
5
fatal error: all goroutines are asleep - deadlock!
goroutine 1 [chan send]:
main.main()
/Users/hui/Project/Go/src/go-basic/main.go:12 +0x59
Process finished with exit code 2

报错原因: 如果Goroutine在一个channel上发送数据,其他的Goroutine应该接收得到数据;如果没有接收,那么程序将在运行时出现死锁。

3.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
package main
import (
"fmt"
)
func main() {
// 创建一个整数型chan
intChan := make(chan int)
// 写入数据(协程写入)
go sendMsg(intChan)
// 接收数据(主线程读取)
a := <- intChan
fmt.Printf("接收数据: %v \n", a)
fmt.Println("运行结束! ")
}
func sendMsg(intChan chan int ){
// 写入
intChan <- 5
fmt.Println("写入数据: 5 ")
}
/** 输出:
写入数据: 5
接收数据: 5
运行结束!
*/

4.普通接收

channel接收同样使用特殊的操作符<-

4.1 阻塞接收语法

1
2
3
4
// 方式一: ch 指的是通道变量
data := <- ch
//方式二: data 表示接收到的数据。未接收到数据时,data为channel类型的零值,ok(布尔类型)表示是否接收到数据
data,ok := <- ch

执行该语句时channel将会阻塞,直到接收到数据并赋值给data变量。

4.2 忽略接收语法

1
<- ch

执行该语句时channel将会阻塞。其目的不在于接收channel中数据,而是为了阻塞Goroutine

如果Goroutine正在等待从channel接收数据,而其他Goroutine并没有写入数据时程序将会死锁。

5. 循环接收

循环接收数据,需要配合使用关闭channel,借助普通for循环和for ... range语句循环接收多个元素。遍历channel,遍历的结果就是接收到的数据,数据类型就是channel的数据类型。普通for循环接收channel数据,需要有break循环的条件;for … range会自动判断出channel已关闭,而无须通过判断来终止循环。

5.1 使用普通for接收

方式一: data := <- ch

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"
func main() {
// 创建一个整数型chan
intChan := make(chan int)
// 写入数据(协程写入)
go func(cha chan int) {
// 写入
for i := 1; i < 5; i++ {
intChan <- i
fmt.Printf("写入数据 -> %v \n", i)
}
// 关闭通道
close(intChan)
}(intChan)

// 方式一: data := <- ch
for {
// 接收数据
out := <-intChan
// 判断通道是否关闭
//如果通道关闭,则out为通道类型的零值,这里是int型,所以是0
if out == 0 {
fmt.Println("通道已关闭")
break
}
fmt.Printf("接收数据 ==> %v \n", out)
}
fmt.Println("程序运行结束!")
}
/** 输出:
写入数据 -> 1
接收数据 ==> 1
接收数据 ==> 2
写入数据 -> 2
写入数据 -> 3
接收数据 ==> 3
接收数据 ==> 4
写入数据 -> 4
通道已关闭
程序运行结束!
*/

方式二: data,ok := <- ch

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
package main
import "fmt"
func main() {
// 创建一个整数型chan
intChan := make(chan int)
// 写入数据(协程写入)
go func(cha chan int) {
// 写入
for i := 1; i < 5; i++ {
intChan <- i
fmt.Printf("写入数据 -> %v \n", i)
}
// 关闭通道
close(intChan)
}(intChan)

// 方式二: data,ok := <- ch
for {
// 接收数据
out,ok := <-intChan
// 判断通道是否关闭,如果通道关闭,则ok为false
if !ok {
fmt.Println("通道已关闭")
break
}
fmt.Printf("接收数据 ==> %v \n", out)
}
fmt.Println("程序运行结束!")
}
/** 输出:
写入数据 -> 1
接收数据 ==> 1
接收数据 ==> 2
写入数据 -> 2
写入数据 -> 3
接收数据 ==> 3
接收数据 ==> 4
写入数据 -> 4
通道已关闭
程序运行结束!
*/

5.2 使用for…range接收

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
package main
import "fmt"
func main() {
// 创建一个整数型chan
intChan := make(chan int)
// 写入数据(协程写入)
go func(cha chan int) {
// 写入
for i := 1; i < 5; i++ {
intChan <- i
fmt.Printf("写入数据 -> %v \n", i)
}
// 关闭通道
close(intChan)
}(intChan)
// 使用 for...range接收
for data := range intChan {
fmt.Printf("接收数据 ==> %v \n", data)
}
fmt.Println("程序运行结束!")
}
/** 输出:
写入数据 -> 1
接收数据 ==> 1
接收数据 ==> 2
写入数据 -> 2
写入数据 -> 3
接收数据 ==> 3
接收数据 ==> 4
写入数据 -> 4
程序运行结束!
*/

6. Channle的阻塞特性

6.1 特性如下

  • channel默认是阻塞的。
  • 当数据被发送到channel时会发生阻塞,直到有其他Goroutine从该channel中读取数据。
  • 当从channel读取数据时,读取也会被阻塞,直到其他Goroutine将数据写入该channel

6.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
package main
import "fmt"
func main() {
// 创建一个整数型chan
intChan := make(chan int)
// 创建一个用于阻塞的chan
boolChan := make(chan bool)

// 创建一个写入数据的协程
go func(cha chan int) {
// 写入
intChan <- 50
fmt.Println("写入数据50")
// 关闭通道
close(intChan)
}(intChan)

// 创建一个读取数据的协程
go func(intChan chan int, boolChan chan bool) {
data,ok := <- intChan
if ok {
fmt.Printf("读取到数据 -> %v \n", data)
// 读取到数据后,给boolChan写入值
boolChan <- true
// 关闭用于的阻塞的chan
close(boolChan)
}
}(intChan,boolChan)
// 忽略接收,达到阻塞的效果。(如果不阻塞,则会直接输出: 程序运行结束!,不会等待协程执行)
<- boolChan
fmt.Println("程序运行结束!")
}
/** 输出
写入数据50
读取到数据 -> 50
程序运行结束!
*/

阻塞channel等待匿名函数的Goroutine运行结束,防止主函数的Goroutine退出而导致匿名函数的Goroutine提前退出。

7.关闭Channel

发送方写入完毕后需要主动关闭channel,用于通知接收方数据传递完毕。接收方通过data,ok := <- ch判断channel是否关闭,如果ok=false,则表示channel已经被关闭。

7.1 使用示例

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
package main
import "fmt"
func main() {
// 创建一个整数型chan
intChan := make(chan int)
// 创建一个写入channel的协程
go func(intChan chan int) {
intChan <- 10
intChan <- 20
// 关闭通道
close(intChan)
}(intChan)

// 读取数据
a := <- intChan
fmt.Printf("接收数据: %v \n",a)
b := <- intChan
fmt.Printf("接收数据: %v \n",b)

// 此时的Chan已经关闭,而且里面的数据也都已经取完
c := <- intChan
fmt.Printf("接收数据: %v \n",c)
fmt.Println("程序运行结束!")
}
/** 输出
接收数据: 10
接收数据: 20
接收数据: 0
程序运行结束!
*/

又上面示例可以看出: 可以从关闭后的channel中继续读取数据,取到的值为该类型的零值。比如整型是:0; 字符串是:””

7.2 向已关闭的chan写入数据,会崩溃

往关闭的channel中写入数据会报错:panic: send on closed 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
package main
import "fmt"
func main() {
// 创建一个整数型chan
intChan := make(chan int)
// 创建一个写入channel的协程
go func(intChan chan int) {
intChan <- 10
// 关闭通道
close(intChan)
// 向已关闭的chan 继续写入数据,会报错;
intChan <- 20
}(intChan)
// 读取数据
a := <- intChan
fmt.Printf("接收数据: %v \n",a)
b := <- intChan
fmt.Printf("接收数据: %v \n",b)
fmt.Println("程序运行结束!")
}
/** 输出:
接收数据: 10
接收数据: 0
panic: send on closed channel

goroutine 18 [running]:
main.main.func1(0xc000100060)
/Users/hui/Project/Go/src/go-basic/main.go:14 +0x5f
created by main.main
/Users/hui/Project/Go/src/go-basic/main.go:10 +0x6a

Process finished with exit code 2
*/

7.3 重复关闭chan,会崩溃

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
package main]
import "fmt"
func main() {
// 创建一个整数型chan
intChan := make(chan int)

// 创建一个写入channel的协程
go func(intChan chan int) {
intChan <- 10
// 关闭通道
close(intChan)
}(intChan)

// 读取数据
a := <- intChan
fmt.Printf("接收数据: %v \n",a)
b := <- intChan
fmt.Printf("接收数据: %v \n",b)
// 重复关闭(此处会报错)
close(intChan)
fmt.Println("程序运行结束!")
}
/** 输出
接收数据: 10
接收数据: 0
panic: close of closed channel

goroutine 1 [running]:
main.main()
/Users/hui/Project/Go/src/go-basic/main.go:22 +0x1cc

Process finished with exit code 2
*/

8.缓冲Channel

默认创建的都是非缓冲channel,读写都是即时阻塞。缓冲channel自带一块缓冲区,可以暂时存储数据,如果缓冲区满了,就会发生阻塞。缓冲通道在发送时无需等待接收方接收即可完成发送过程,并且不会发生阻塞,只有当缓冲区满时才会发生阻塞。同理,如果缓冲通道中有数据,接收时将不会发生阻塞,直到通道中没有数据可读时,通道将会再度阻塞。

8.1 语法

1
2
// 声明 n:代表缓冲区大小
cha1 := make(chan T,n)

8.2 使用示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
package main
import (
"fmt"
"time"
)
func main() {
fmt.Printf("开始时间: %v \n",time.Now().Unix())
// 创建一个缓冲区为2的整数型chan
intChan2 := make(chan int,2)
// 不会发生阻塞,因为缓冲区未满
intChan2 <- 100
fmt.Printf("结束时间: %v \n",time.Now().Unix())
fmt.Printf("intChan2 类型: %T 缓冲大小: %v \n",intChan2,cap(intChan2))
fmt.Println("程序运行结束!")
}
/**输出:
开始时间: 1607496281
结束时间: 1607496281
intChan2 类型: chan int 缓冲大小: 2
程序运行结束!
*/

9.单向Channel

channel默认都是双向的,即可读可写。定向channel也叫单向channel,只读或只写。直接创建单向channel没有任何意义。通常的做法是创建双向channel,然后以单向channel的方式进行函数传递。

9.1 介绍

1
2
3
4
// 只读
ch <- chan T
// 只写
ch chan <- T

9.1 使用

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
package main
import (
"fmt"
"time"
)
func main() {
// 创建一个整数型chan
intChan := make(chan int)
go writeChan(intChan)
go readChan(intChan)
time.Sleep(50 * time.Millisecond)
fmt.Println("运行结束")

}

// 定义只读通道的函数
func readChan( ch <- chan int) {
for data := range ch {
fmt.Printf("读出数据: %v \n",data)
}
}
// 定义只写通道的函数
func writeChan( ch chan <- int){
for i:= 1; i< 5 ; i++ {
ch <- i
fmt.Printf("写入数据: %v \n",i)
}
close(ch)
}

10.计时器与channel

计时器类型表示单个事件。当计时器过期时,当前时间将被发送到c上(c是一个只读channel <-chan time.Time,该channel中放入的是Timer结构体),除非计时器是After()创建的。计时器必须使用NewTimer()After()创建。

10.1 NewTimer

NewTimer()创建一个新的计时器,它会在至少持续时间d之后将当前时间发送到其channel上。

使用示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package main
import (
"fmt"
"time"
)
func main() {
// 创建一个计时器
timer := time.NewTimer(5 * time.Second)
fmt.Printf("开始时间 %v \n",time.Now())
// 此处会阻塞5秒
out := <- timer.C
fmt.Printf("变量out-> 类型: %T 值:%v \n",out,out)
fmt.Printf("开始时间 %v \n",time.Now())
}
/** 输出:
开始时间 2020-12-10 10:53:22.979673 +0800 CST m=+0.000174275
变量out-> 类型: time.Time 值:2020-12-10 10:53:27.980079 +0800 CST m=+5.000489969
开始时间 2020-12-10 10:53:27.980264 +0800 CST m=+5.000674880
*/

10.2 After

After()函数相当于NewTimer(d). C,如下源码:

1
2
3
func After(d Duration) <-chan Time {
return NewTimer(d).C
}

使用示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package main
import (
"fmt"
"time"
)
func main() {
// 创建一个计时器,返回的是chan
ch := time.After(5 * time.Second)
fmt.Printf("开始时间 %v \n",time.Now())
// 此处会阻塞5秒
out := <- ch
fmt.Printf("变量out-> 类型: %T 值:%v \n",out,out)
fmt.Printf("开始时间 %v \n",time.Now())
}
/** 输出
开始时间 2020-12-10 11:01:07.272154 +0800 CST m=+0.000153152
变量out-> 类型: time.Time 值:2020-12-10 11:01:12.273034 +0800 CST m=+5.000956630
开始时间 2020-12-10 11:01:12.273153 +0800 CST m=+5.001076196
*/