channel
channel是goroutine进行通信的管道,数据从一端发送到另一端,通过通道接收。
不要通过共享内存来通信,而应该通过通信来共享内存
go语言中,要传递某个数据给另一个goroutine,可以把这个数据封装成一个对象,然后把这个对象的指针传入某个channel中,另外一个goroutine从这个channel中读出这个指针,并处理其指向的内存对象。Go从语言层面保证同一个时间只有一个goroutine能访问channel中的数据,所以go的做法就是使用channel来通信,通过通信来传递内存中的数据,是的内存数据在不同的goroutine中传递,而不是使用共享内存来通信。
注意点
- 1.用于goroutine,传递消息的
- 2.通道,每个都有相关联的数据类型,nil chan无法使用,类似于nil map,不能直接储存键值对
- 3.使用通道传递数据:<-,根据箭头方法进行数据传递
- 4.阻塞:
发送数据:chan <- data
,阻塞的,直到另一条goroutine读取数据来解除阻塞
读取数据:data <- chan
,阻塞的,直到另一条goroutine写出数据来解除阻塞 - 5.本身channel就是同步的,意味着同一时间,只能有一条goroutine来操作
最后:通道是goroutine之间的连接,所以通道的发送和接收必须处在不同的goroutine中。
创建通道
通道的零值为nil。nil通道没有意义,因此通道必须使用类似map和切片的方式来定义
// 声明通道
var 通道名 chan 数据类型
// 创建通道:如果通道为nil(就是不存在),就需要先创建通道
通道名 = make(chan 数据类型)
发送和接收
data := <- a // 从通道a中读取数据
a <- data // 向通道a中写入数据
v, ok := <- a // 从通道a中读取数据并获取通道是否关闭
阻塞
一个通道发送和接收数据,默认是阻塞的。当一个数据被发送到通道时,在发送语句中被阻塞,直到另一个goroutine从该通道读取数据。相应的,当从通道读取数据的时候,读取被阻塞,直到一个goroutine将数据写入该通道。channel的这个特性可以帮助开发者简单的完成通信,无需像其他语言一样使用锁或者条件变量。
func main() {
ch1 := make(chan int)
go func() {
fmt.Println("子goroutine开始运行...")
//time.Sleep(3 * time.Second)
data := <- ch1
fmt.Println("data:", data)
}()
time.Sleep(5 * time.Second)
ch1 <- 10
time.Sleep(1 * time.Second)
fmt.Println("main___over....")
}
输出结果
子goroutine开始运行...
这里执行了
data: 10
main___over....
在子goroutine中的data := <- ch1
这一行,读取数据的时候被阻塞了,直到主goroutine中ch1 <- 10
向channel中写入数据才能完成读操作,读出数据并打印出来。
死锁
如果goroutine在一个通道上发送数据,那么预计其他的goroutine应该接收数据,如果没有一个其他的goroutine来接收这个数据,那么程序运行的时候就会出现死锁;同样,如果goroutine在接收一个通道上的数据,而没有其他的goroutine向该通道写入数据,那么也会陷入死锁。
func main() {
ch := make(chan int)
ch <- 5
}
报错信息
fatal error: all goroutines are asleep - deadlock!
goroutine 1 [chan send]:
main.main()
D:/1important/Go/GoPath/src/Godemo/W1/demo/channel/demo3_channel死锁.go:5 +0x57
关闭通道
发送者可以通过关闭通道来通知接收方不会有更多的数据被发送到channel上
close(ch)
接收者可以通过接收通道的数据时使用额外的变量来检查通道是否已经关闭
v, ok := <- ch
类似map操作,存储key,value键值对
v, ok := map[key] // 根据key从map中获取value,如果key存在,v就是对应的数据,如果key不存在,v是默认值
在上面的语句中,如果ok的值是true,表示成功的从通道中读取了一个数据value。如果ok是false,这意味着我们正在从一个封闭的通道中读取数据,读取到的值将会是通道类型的零值。
使用range循环读取通道中的数据
func main() {
ch1 := make(chan int)
go sendData(ch1)
for v := range ch1 {
fmt.Println("读取数据:",v)
}
fmt.Println("main...over...")
}
func sendData(ch1 chan int) {
for i := 0; i < 10; i++ {
time.Sleep(1*time.Second)
ch1 <- i
}
close(ch1)
}
缓冲通道
缓冲通道就是指一个带有缓冲区的通道。发送数据到一个缓冲通道只有当缓冲区满了的情况下才能被阻塞。类似的,从缓冲通道中接受数据只有当缓冲区空的时候才会别阻塞。
可以通过将额外的参数传递给make函数来创建缓冲通道,指定缓冲区大小
ch := make(chan 数据类型, 缓冲区大小)
上述语法的容量应该大于0,以便通道具有缓冲区。默认情况下,无缓冲通道的容量为0,因此在之前创建通道时省略了容量参数
func main() {
ch := make(chan string,4)
go sendData(ch)
for {
v, ok := <- ch
if !ok {
fmt.Println("读完了。。。",ok)
break
}
fmt.Println("\t读取的数据是:",v)
}
fmt.Println("main...over...")
}
func sendData(ch chan string) {
for i := 0; i < 10; i++ {
ch <- "数据" + strconv.Itoa(i)
fmt.Printf("子goroutine中写出第 %d 个数据\n", i)
}
close(ch)
}
定向通道
定向通道也就是单向通道。这种通道只能发送或接收数据
ch1 := make(chan int) // 双向,读,写
ch2 := make(chan <- int) // 单向,只能写,不能读
ch3 := make(chan <- int) // 单向,只能读,不能写
双向通道可以转化为单向通道,但是单向通道无法转化为双向通道
ch1 := make(chan int)
ch2 := <-chan int(ch1)
ch3 := chan<- int(ch1)
对于单向通道的应用主要是用来约束不同业务对通道的权限控制,比如在消费者/生产者模型中,单向通道就被应用在保证生产者只能向通道中写入数据和消费者只能从通道中读取数据
func main() {
// 此处是双向通道
channel := make(chan int)
go producer(channel)
consumer(channel)
}
// 传入生产者的时候被转化为单向只写通道
func producer(send chan<- int) {
for i := 0; i < 10; i++ {
send <- i
}
close(send)
}
// 传入消费者的时候被转化为单向只读通道
func consumer(receive <-chan int) {
for num := range receive {
fmt.Println("receive", num)
}
}