ThinkChat2.0新版上线,更智能更精彩,支持会话、画图、阅读、搜索等,送10W Token,即刻开启你的AI之旅 广告
## 一、channel是什么 * goroutine运行在相同的地址空间,因此访问共享内存必须做好同步,处理好线程安全问题 * goroutine奉行通过通信来共享内存,而不是共享内存来通信 * channel是一个引用类型,用于多个goroutine通讯,其内部实现了同步,确保并发安全 * channel主要用域多个协程之间通信 ## 二、channel的基本使用 * channel可以用内置make()函数创建 * 定义一个channel时,也需要定义发送到channel的值的类型 make(chan 类型) make(chan 类型, 容量) * 当 capacity= 0 时,channel 是无缓冲阻塞读写的,当capacity> 0 时,channel 有缓冲、是非阻塞的,直到写满 capacity个元素才阻塞写入 * channel通过操作符<-来接收和发送数据,发送和接收数据语法: channel <- value //发送value到channel <-channel //取出数据扔掉 x := <-channel //取出数据,给x x, ok := <-channel //功能同上,顺便检查一下通道 ## 三、channel实现主协程和子协程通信 ~~~ package main import ( "fmt" ) func main() { // 创建协程 h := make(chan int) // 创建子协程 go func(){ defer fmt.Println("子协程结束") fmt.Println("子协程正在运行") h <- 999 }() num := <-h fmt.Println("num=",num) fmt.Println("main结束") } ~~~ 运行结果: 子协程正在运行 子协程结束 num= 999 main结束 ## 四、无缓冲 channel ~~~ package main import ( "fmt" "time" ) func main() { //无缓冲通道 c := make(chan int, 0) fmt.Printf("len(c)=%d,cap(c)=%d\n", len(c), cap(c)) //子协程存数据 go func() { defer fmt.Println("子协程结束") //向通道添加数据 for i := 0; i < 3; i++ { c <- i fmt.Printf("子协程正在运行[%d]:"+"len(c)=%d,cap(c)=%d\n", i, len(c), cap(c)) } }() time.Sleep(2 * time.Second) //主协程取数据 for i := 0; i < 3; i++ { num := <-c fmt.Println("num = ", num) } fmt.Println("主协程结束") } ~~~ 运行结果: len(c)=0,cap(c)=0 num = 0 子协程正在运行[0]:len(c)=0,cap(c)=0 子协程正在运行[1]:len(c)=0,cap(c)=0 num = 1 num = 2 主协程结束 ## 五、有缓冲 channel ~~~ package main import ( "fmt" "time" ) func main() { //有缓冲通道 c := make(chan int, 3) fmt.Printf("len(c)=%d,cap(c)=%d\n", len(c), cap(c)) //子协程存数据 go func() { defer fmt.Println("子协程结束") //向通道添加数据 for i := 0; i < 3; i++ { c <- i fmt.Printf("子协程正在运行[%d]:"+"len(c)=%d,cap(c)=%d\n", i, len(c), cap(c)) } }() time.Sleep(2 * time.Second) //主协程取数据 for i := 0; i < 3; i++ { num := <-c fmt.Println("num = ", num) } fmt.Println("主协程结束") } ~~~ 执行结果: len(c)=0,cap(c)=3 子协程正在运行[0]:len(c)=1,cap(c)=3 子协程正在运行[1]:len(c)=2,cap(c)=3 子协程正在运行[2]:len(c)=3,cap(c)=3 子协程结束 num = 0 num = 1 num = 2 主协程结束 ## 六、close() * 可以通过内置的close()函数关闭channel * 通知取通道数据的地方,没有数据了,不用取数据了 #### 没有close() ~~~ package main import ( "fmt" ) func main() { c := make(chan int) //子协程存数据 go func() { for i := 0; i < 5; i++ { c <- i } }() //主协程取数据 for { if data, ok := <-c; ok { fmt.Println(data) } else { break } } fmt.Println("主协程结束") } ~~~ 这里的运行结果会出现报错,因为我们的主协程是死循环,会一致执行 #### 有close() ~~~ package main import ( "fmt" ) func main() { c := make(chan int) //子协程存数据 go func() { for i := 0; i < 5; i++ { c <- i } close(c) }() //主协程取数据 for { if data, ok := <-c; ok { fmt.Println(data) } else { break } } fmt.Println("主协程结束") } ~~~ 运行结果: 0 1 2 3 4 主协程结束 注:这里子协程会有一个通知主协程的动作,如果没有更多数据,子协程会通知主协程,没有更多数据可以结束取数据的动作了 ## 七、单方向的channel ### 1、概念 * 默认情况下,通道是双向的,也就是,既可以往里面发送数据也可以接收数据 * go可以定义单方向的通道,也就是只发送数据或者只接收数据,声明如下 var ch1 chan int //正常 var ch2 chan<- float64 //只能写float64 var ch3 <-chan int //只能读int类型数据 * 可以将 channel 隐式转换为单向队列,只收或只发,不能将单向 channel 转换为普通channel #### 普通实例1 ~~~ package main import ( "fmt" ) func main() { // 定义一个正常的通道 c := make(chan int,3) // 转换为只写通道 var send chan<- int =c // 转化为只读 var recv <-chan int =c // 写数据 send <- 11 // 读数据 <- recv fmt.Println("结束") } ~~~ #### 单方向的channel 可以用作生产者和消费者 ~~~ package main import "fmt" //生产者,只写 func producter(out chan<- int) { defer close(out) for i := 0; i < 5; i++ { out <- i } } //消费者,只读 func consumer(in <-chan int) { for num := range in { fmt.Println(num) } } func main() { //正常通道 c := make(chan int) //生产者 //可以将 channel 隐式转换为单向队列,只收或只发,不能将单向 channel 转换为普通channel go producter(c) //消费者 consumer(c) fmt.Println("done") } ~~~ 执行结果: 0 1 2 3 4 done 注:可以将 channel 隐式转换为单向队列,只收或只发,不能将单向 channel 转换为普通channel