go channel学习笔记
什么是channel
go channel 是go语言一种核心数据类型,可以理解是一种管道,并发核心单元可以通过channel发送或接收数据进行通讯,实现并发同步。
channel 类型
channel类型是可以有方向的,假设T是一种类型:
- chan T是双向channel类型,编译器允许对双向channel同时进行发送和接收。
- chan<- T是只写channel类型,编译器只允许往channel里面发送数据。
- <-chan T是只读channel类型,编辑器只允许从channel里面接收数据。
在go语言中,通过chan创建通道,创建语法:
channel_name 表示声明的管道名,Type表示管道传输的数据类型。管道支持数据类型包括:int,string,struct等。channel1 := make(chan int) //双向channel,可读写 channel2 := make(chan<- int) //单向只写channel channel3 := make(<-chan int) //单向只读channel
channel 分类
channel分无缓冲类型和带缓冲类型:
- 无缓冲类型channel
一个线程向这个channel发送了消息后,会阻塞当前的这个线程,直到其他线程去接收这个channel的消息。创建语法:intchan := make(chan int)
- 缓冲类型channel
带缓冲的channel,是可以指定缓冲的消息数量,当消息数量小于指定值时,不会出现阻塞,超过之后才会阻塞,需要等待其他线程去接收channel处理。创建语法:intchan := make(chan int,3)
channel的一些操作
假设ch是一个通道。
- close(关闭一个通道)
注意:如果ch是一个nil通道,或者ch已经被关闭,上述close(ch)会触发panicclose(ch)
- 向通道ch发送一个值
注意:ch只能是双向通道或者是单向只写通道。ch<-c
- 从通道ch接收一个值
注意:通道ch只能是双向通道或者是单向只读通道c <-ch
- 获取通道容量
cap(ch)
- 获取通道长度
注意:len函数返回是当前通道元素个数,cap函数返回的是当前通道总共能存放元素个数。len(ch)
一些channel的使用例子
- 非缓冲通道实现数据同步
package main
import (
"fmt"
"time"
)
func main() {
c := make(chan int)
done := make(chan string)
go func(ch chan<- int, x int) {
time.Sleep(time.Second * 5)
ch <- x * x * x
}(c, 10)
go func(ch <-chan int) {
data := <-ch
fmt.Println(data)
s := "程序结束"
done <- s
}(c)
fmt.Println("程序开始")
fmt.Println(<-done)
}
结果输出:
程序开始
1000
程序结束
- 非缓冲通道实现超时控制
package main
import (
"fmt"
"time"
)
func main() {
select {
case <-dowork():
fmt.Println("任务在规定时间内完成!")
case <-time.After(time.Second * 2):
fmt.Println("任务超时了!!!")
}
}
func dowork() <-chan int {
ch := make(chan int)
go func() {
fmt.Println("开始工作")
time.Sleep(time.Second * 3)
ch <- 0
}()
return ch
}
结果输出:
开始工作
任务超时了!!!
- 带缓冲通道实现生产者-消费者模型
package main
import (
"fmt"
"time"
)
func producer(ch chan int) {
for i := 0; i < 10; i++ {
ch <- i
}
close(ch)
}
func consumer(ch chan int, done chan string) {
for i := 0; i < 5; i++ {
go func(id int) {
for {
data, ok := <-ch
if !ok {
done <- "done"
return
} else {
fmt.Printf("消费者 %v,完成任务 %v\n", id, data)
time.Sleep(time.Second * 2)
}
}
}(i)
}
}
func main() {
done := make(chan string)
ch := make(chan int, 10)
go producer(ch)
go consumer(ch, done)
<-done
fmt.Println("任务完成")
}
结果输出:
消费者 4,完成任务 0
消费者 1,完成任务 1
消费者 2,完成任务 2
消费者 3,完成任务 3
消费者 0,完成任务 4
消费者 0,完成任务 5
消费者 3,完成任务 8
消费者 2,完成任务 6
消费者 1,完成任务 9
消费者 4,完成任务 7
任务完成
- 带缓冲通道实现并发数量控制
package main
import (
"fmt"
"time"
)
func handleEvent(done chan string, task chan bool) {
for i := 0; i < 10; i++ {
task <- true
go func(id int) {
fmt.Printf("处理事件 %v\n", id)
time.Sleep(time.Second * 1)
<-task
if id == 9 {
done <- "done"
}
}(i)
}
}
func main() {
done := make(chan string)
task := make(chan bool, 2) //并法数控制为2
go handleEvent(done, task)
<-done
fmt.Println("任务完成")
}
结果输出:
处理事件 1
处理事件 0
处理事件 2
处理事件 3
处理事件 5
处理事件 4
处理事件 7
处理事件 6
处理事件 8
处理事件 9
任务完成
channel使用注意事项
- 关闭一个nil通道或者一个已经关闭的通道将产生一个panic。
- 向一个已关闭的通道发送数据也将导致panic。
- 向一个nil通道发送数据或者从一个nil通道接收数据将使当前协程永久阻塞。
本作品采用《CC 协议》,转载必须注明作者和本文链接