并发编程-通道使用
# 1.什么是Channel?
channel即Go的通道,是协程之间的通信机制。一个channel是一条通信管道,它可以让一个协程通过它给另一个协程发送数据。每个channel都需要指定数据类型,即channel可发送数据的类型。Go语言主张通过数据传递来实现共享内存,而不是通过共享内存来实现数据传递。
# 2. 创建Channel
# 2.1 语法
channel是引用类型,需要使用make()进行创建。
// 声明方式1
var cha1 chan 数据类型
cha1 = make(chan 数据类型)
// 声明方式2
cha1 := make(chan 数据类型)
2
3
4
5
6
# 2.2 使用示例
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
*/
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# 3.发送数据
# 3.1 语法
通过channel发送数据需要使用特殊的操作符<-,需要注意的是: channel发送的值的类型必须与channel的元素类型一致。
channel变量名 <- 值
# 3.2 错误使用示例
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)
}
2
3
4
5
6
7
8
9
10
11
12
13
14
上面示例运行会死锁,报错内容如下:
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
2
3
4
5
报错原因: 如果Goroutine在一个channel上发送数据,其他的Goroutine应该接收得到数据;如果没有接收,那么程序将在运行时出现死锁。
# 3.3 正确使用示例
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
运行结束!
*/
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# 4.普通接收
channel接收同样使用特殊的操作符<-。
# 4.1 阻塞接收语法
// 方式一: ch 指的是通道变量
data := <- ch
//方式二: data 表示接收到的数据。未接收到数据时,data为channel类型的零值,ok(布尔类型)表示是否接收到数据
data,ok := <- ch
2
3
4
执行该语句时channel将会阻塞,直到接收到数据并赋值给data变量。
# 4.2 忽略接收语法
<- 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
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
通道已关闭
程序运行结束!
*/
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
方式二: data,ok := <- ch
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
通道已关闭
程序运行结束!
*/
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
# 5.2 使用for...range接收
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
程序运行结束!
*/
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
# 6. Channle的阻塞特性
# 6.1 特性如下
channel默认是阻塞的。- 当数据被发送到
channel时会发生阻塞,直到有其他Goroutine从该channel中读取数据。 - 当从
channel读取数据时,读取也会被阻塞,直到其他Goroutine将数据写入该channel。
# 6.2 特性使用
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
程序运行结束!
*/
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
阻塞channel等待匿名函数的Goroutine运行结束,防止主函数的Goroutine退出而导致匿名函数的Goroutine提前退出。
# 7.关闭Channel
发送方写入完毕后需要主动关闭channel,用于通知接收方数据传递完毕。接收方通过data,ok := <- ch判断channel是否关闭,如果ok=false,则表示channel已经被关闭。
# 7.1 使用示例
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
程序运行结束!
*/
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
又上面示例可以看出: 可以从关闭后的
channel中继续读取数据,取到的值为该类型的零值。比如整型是:0; 字符串是:""
# 7.2 向已关闭的chan写入数据,会崩溃
往关闭的channel中写入数据会报错:panic: send on closed channel。导致程序崩溃。
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
*/
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
# 7.3 重复关闭chan,会崩溃
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
*/
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
# 8.缓冲Channel
默认创建的都是非缓冲channel,读写都是即时阻塞。缓冲channel自带一块缓冲区,可以暂时存储数据,如果缓冲区满了,就会发生阻塞。缓冲通道在发送时无需等待接收方接收即可完成发送过程,并且不会发生阻塞,只有当缓冲区满时才会发生阻塞。同理,如果缓冲通道中有数据,接收时将不会发生阻塞,直到通道中没有数据可读时,通道将会再度阻塞。
# 8.1 语法
// 声明 n:代表缓冲区大小
cha1 := make(chan T,n)
2
# 8.2 使用示例
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
程序运行结束!
*/
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# 9.单向Channel
channel默认都是双向的,即可读可写。定向channel也叫单向channel,只读或只写。直接创建单向channel没有任何意义。通常的做法是创建双向channel,然后以单向channel的方式进行函数传递。
# 9.1 介绍
// 只读
ch <- chan T
// 只写
ch chan <- T
2
3
4
# 9.1 使用
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)
}
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
# 10.计时器与channel
计时器类型表示单个事件。当计时器过期时,当前时间将被发送到c上(c是一个只读channel <-chan time.Time,该channel中放入的是Timer结构体),除非计时器是After()创建的。计时器必须使用NewTimer()或After()创建。
# 10.1 NewTimer
NewTimer()创建一个新的计时器,它会在至少持续时间d之后将当前时间发送到其channel上。
使用示例:
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
*/
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 10.2 After
After()函数相当于NewTimer(d). C,如下源码:
func After(d Duration) <-chan Time {
return NewTimer(d).C
}
2
3
使用示例
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
*/
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19