Channel通道
# 一、Channel
通道
通道可以被认为是Goroutine
通信的管道.类似于管道中的水从一端到另一端的流动,数据可以从一端发送到另一端,通过通道接收.
当多个Goroutine
想实现共享数据的时候,虽然也提供了传统的同步机制,但是Go
语言强烈建议的是使用Channel
通道来实现Goroutines
之间的通信.
"不要通过共享内存来通信,而应该通过通信来共享内存"这是语句风靡
golang
社区的经典语
Go
语言中,要传递某个数据给另一个goroutine(协程)
,可以把一个数据封装成一个对象,然后把这个对象的指针传入某个channel
中,另外一个goroutine
从这个channel
中读出这个指针,并处理其指向的内存对象.Go
从语言层面保证同一个时间只有一个goroutine
能够访问channel
里面的数据,为开发者提供了一种优雅加单的工具,所以Go
的做法就是使用channel
来通信,通过通信来传递内存数据,使得内存数据在不同的goroutine
中传递,而不是使用共享内存来通信.
# 二、什么是通道
通道是goroutine
之间的通道.它可以让goroutine
之间相互通信.
每个通道都有与其相关的类型.该类型是通道允许传输的数据类型.(通道的的零值为nil
.nil
通道没有任何用处,因此通道必须使用类似于map
和切片的方法来定义.)
# 通道的声明
声明一个通道和定义一个变量的语法一样:
//声明通道
var 通道名 chan 数据类型
//创建通道:如果通道为nil(就是不存在),就需要先创建通道
通道名 = make(chan 数据类型)
2
3
4
示例代码
package main
import "fmt"
func main() {
var a chan int
if a == nil {
fmt.Println("channel 是 nil 的, 不能使用,需要先创建通道。。")
a = make(chan int)
fmt.Printf("数据类型是: %T", a)
}
}
2
3
4
5
6
7
8
9
10
11
12
运行结果
channel 是 nil 的,不能使用,需要先创建通道...
数据类型: chan int
2
也可以简短声明
a := make(chan int)
# channel
的数据类型
channel
是引用类型的数据,在作为参数传递的时候,传递的是内存地址.
示例代码
package main
import (
"fmt"
)
func main() {
ch1 := make(chan int)
fmt.Printf("%T,%p\n",ch1,ch1)
test1(ch1)
}
func test1(ch chan int){
fmt.Printf("%T,%p\n",ch,ch)
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
运行结果
能够看到,ch
和ch1
的地址是一样的,说明它们是同一个通道.
# 通道的注意点
Channel
通道在使用的时候,有以下几个注意点:
- 1.用于
goroutine
,传递消息的. - 2.通道,每个都有相关联的数据类型,
nil chan
,不能使用,类似于nil map
,不能直接存储键值对 - 3.使用通道传递数据:
<- chan <- data
,发送数据到通道.向通道中写数据data <- chan
,从通道中获取数据.从通道中读数据 - 4.阻塞: 发送数据:
chan <- data
,阻塞的,直到另一条goroutine
,读取数据来解除阻塞 读取数据:data <- chan
,也是阻塞的.直到另一条goroutine
,写出数据解除阻塞 - 5.本身
channel
就是同步的,意味着同一时间,只能有一条goroutine
来操作.
最后: 通道是goroutine
之间的连接,所以通道的发送和接收必须处在不同的goroutine
中.
# 三、通道的使用语法
# 发送和接收
发送和接收的语法
data := <- a // read from channel a
a <- data //write to channel a
2
在通道上箭头的方向指定数据是发送还是接收.
另外:
v,ok := <- a //从一个channel中读取
# 发送和接收默认是阻塞的
一个通道发送和接收数据,默认是阻塞的.当一个数据被发送到通道时,在发送语句中被阻塞,直到另一个Goroutine
从该通道读取数据.相对地,当从通道读取数据时,读取被阻塞,直到一个Goroutine
将数据写入该通道.
这些通道的特性是帮助Goroutine
有效地进行通信,而无需像使用其他编程语言中非常常见的显示锁或条件变量.
示例代码
package main
import "fmt"
func main() {
var ch1 chan bool //声明,没有创建
fmt.Println(ch1) //<nil>
fmt.Printf("%T\n", ch1) //chan bool
ch1 = make(chan bool) //0xc0000a4000,是引用类型的数据
fmt.Println(ch1)
go func() {
for i := 0; i < 10; i++ {
fmt.Println("子goroutine中,i:", i)
}
// 循环结束后,向通道中写数据,表示要结束了。。
ch1 <- true
fmt.Println("结束。。")
}()
data := <-ch1 // 从ch1通道中读取数据
fmt.Println("data-->", data)
fmt.Println("main。。over。。。。")
}
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
运行结果
上面的过程中,先创建了一个chan bool
通道.然后启动一条子Goroutine
,并循环打印10个数字.然后向通道chn1
中写入出入true
.然后在主goroutine
中,从ch1
中读取数据.这一行代码是阻塞的,这意味着在子Goroutine
将数据写入到该通道之前,主goroutine
将不会执行到下一行代码.因此,可以通过channel
实现子goroutine
和主goroutine
之间的通信.当子goroutine
执行完毕前,主goroutine
会因为读取ch1
中的数据而阻塞.从而保证了子goroutine
会先执行完毕.这就消除了对时间的需求.在之前的程序中,要么让主goroutine
进入睡眠,一防止主要的Goroutine
退出.要么通过WaitGroup
来保证子goroutine
先执行完毕,主goroutine
才结束.
示例代码: 一下代码加入了睡眠,可以更好的理解channel的阻塞
package main
import (
"fmt"
"time"
)
func main() {
ch1 := make(chan int)
done := make(chan bool) // 通道
go func() {
fmt.Println("子goroutine执行。。。")
time.Sleep(3 * time.Second)
data := <-ch1 // 从通道中读取数据
fmt.Println("data:", data)
done <- true
}()
// 向通道中写数据。。
time.Sleep(5 * time.Second)
ch1 <- 100
<-done
fmt.Println("main。。over")
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
如下示例,这个程序将打印一个数字的个位数的平方和.
package main
import (
"fmt"
)
func calcSquares(number int, squareop chan int) {
sum := 0
for number != 0 {
digit := number % 10
sum += digit * digit
number /= 10
}
squareop <- sum
}
func calcCubes(number int, cubeop chan int) {
sum := 0
for number != 0 {
digit := number % 10
sum += digit * digit * digit
number /= 10
}
cubeop <- sum
}
func main() {
number := 589
sqrch := make(chan int)
cubech := make(chan int)
go calcSquares(number, sqrch)
go calcCubes(number, cubech)
squares, cubes := <-sqrch, <-cubech
fmt.Println("Final output", squares + cubes)
}
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
运行结果
Final output 1536
# 死锁
使用通道时要考虑的一个重要因素是死锁.如果Goroutine
在一个通道上发送数据,那么预计其他的Goroutine
应该接收数据.如果这种情况不发生,那么程序将在运行时出现死锁.
类似地,如果Goroutine
正在等待从通道接收数据,那么另一些Goroutine
将会在该通道上写入数据,否则程序将会死锁.
示例代码
package main
func main() {
ch := make(chan int)
ch <- 5
}
2
3
4
5
6
报错:
fatal error: all goroutines are asleep - deadlock!
goroutine 1 [chan send]:
main.main()
/Users/ruby/go/src/l_goroutine/demo08_chan.go:5 +0x50
2
3
4
5
在主流的编程语言中为了保证多线程之间共享数据安全性和一致性,都会提供一套基本的同步工具集,如锁,条件变量,原子操作等等.
Go
语言标准库也毫不意外的提供了这些同步机制,使用方式也和其他语言差不多.除了这些基本的同步手段,Go
语言还提供了一种新的同步机制:Channel
,它在Go
语言中是一个像in,float32
等的基本类型,一个channel
可以认为是一个能够在多个Goroutine
之间传递某一类的数据的管道.Go
中的channel
无论是实现机制还是使用场景都和Java
中的BlockingQueue
很接近.
- 01
- AWS NAT-NetWork-Firwalld配置(一)04-09
- 02
- AWS NAT-NetWork-Firwalld配置(二)04-09
- 03
- kubernetes部署minio对象存储01-18