当前位置: 首页 > news >正文

Golang—channel

Golang协程-CSDN博客,了解了Golang中的协程再来讲协程之间如何通过channel进行通信。

了解channel

概念:传送带 / 管道

你可以把 Channel(通道) 想象成一条在协程(Goroutine) 之间传送数据的传送带或者管道

  • 协程(Goroutine):就像工厂里的工人。

  • Channel(通道):就像连接两个工人工作台的传送带。


Channel 的主要作用

  1. 通信(Communication)

    • 这是最基本的作用。一个协程把数据(比如一个零件)放在传送带的一端(发送),另一个协程从传送带的另一端取走这个数据(接收)。数据就成功地从 A 协程传递到了 B 协程。

  2. 同步(Synchronization)

    • 这是 Channel 一个极其重要的“副作用”。它保证了协程之间的执行顺序。

    • 没有缓冲区的 Channel(Unbuffered Channel) 就像一次只能传一个零件的、手递手的传送带。

      • 发送者把零件放上传送带后,必须等待接收者把它取走,才能继续干下一件事(发送下一个数据)。

      • 接收者在零件到达之前,必须等待发送者把零件放上来。

      • 这个过程强制了两个协程在同一个时间点“碰头”,完成了同步。

  3. 防止竞态条件(Preventing Race Conditions)

    • 多个工人(协程)如果同时去操作一个共享的工具箱(共享变量),可能会发生争抢,导致数据错乱。这被称为“竞态条件”。

    • 通过 Channel,我们可以把“操作共享数据”这个任务,变成一个“通过传送带传递任务”的模型。比如,我们指定只有一个特殊的“管理员”协程可以操作工具箱,其他协程如果需要工具,就把请求(数据)通过 Channel 发给管理员,然后等待管理员通过另一个 Channel 把工具(结果)送回来。这样就避免了多个协程直接冲突。


生动的场景举例

假设我们有一个主线程(厂长)和两个协程(工人A和工人B)。

没有 Channel 的世界(混乱):

  • 厂长说:“A去生产零件,B去组装。”

  • A和B同时跑向仓库拿原料,可能会撞在一起(竞态条件)。

  • B可能跑得太快,在A还没生产出零件时,就开始组装空气(数据不同步)。

有 Channel 的世界(井然有序):

场景一:简单的通信与同步(使用无缓冲Channel)

// 创建一个传送带(无缓冲Channel)
ch := make(chan string)// 工人A(协程)
go func() {result := "零件A做好了" // 1. 工人A生产零件ch <- result          // 2. 把零件放上传送带。此时如果没人来接,A就等着(阻塞)fmt.Println("A:我把零件交出去了")
}()// 主线程(厂长)
message := <-ch // 3. 厂长从传送带取下零件。如果零件没到,厂长就等着(阻塞)
fmt.Println("主线程收到了:", message)
// 输出:
// 主线程收到了: 零件A做好了
// A:我把零件交出去了

看,因为 Channel 的同步特性,A:我把零件交出去了 这句话总是在零件被主线程接收之后才打印。

场景二:带缓冲的通信(像一个小型仓库)

// 创建一个能存放2个零件的传送带(缓冲为2的Channel)
ch := make(chan string, 2)// 工人A可以连续放两个零件,而不用马上等别人来取
ch <- "零件1"
ch <- "零件2"
// ch <- "零件3" // 如果此时再放第三个,因为仓库满了,工人A就会阻塞等待// 工人B可以连续取走两个零件
fmt.Println(<-ch) // 零件1
fmt.Println(<-ch) // 零件2

这种 Channel 更侧重于通信的吞吐量,而不是强同步。


总结

所以,通俗地讲,Channel 的作用就是:

它为协程们提供了一条安全、有序的“数据传输管道”。不仅解决了“怎么传”的通信问题,更重要的是通过“等待”机制,巧妙地解决了“什么时候传”的同步问题,从而让并发编程变得简单和安全。

channel的基本定义与使用

定义

make(chan Type)    //无缓冲 等价于make(chan Type,0)
make(chan Type, capacity) //有缓冲
channel <- value    //发送value到channel
<- channel          //接收并将其丢弃
x := <- channel     //从channel中接收数据,并赋值给x
x, ok := <- channel //功能同上,同时检查通道是否已关闭或者是否为空

使用

import ("fmt"
)func main() {//定义一个channelc := make(chan int)//启动一个goroutine,向channel中发送数据go func() {defer fmt.Println("goroutine end")fmt.Println("goroutine start to send data")c <- 666 //向channel中发送数据666}()num := <-c //从channel中接收数据并赋值给numfmt.Println("main received data:", num)fmt.Println("main goroutine end")
}---------------------------------------------------------
PS D:\GoProject\firstGoProject> go run firstGoProject.go
goroutine start to send data
goroutine end
main received data: 666
main goroutine end

channel的有缓冲和无缓冲

无缓冲

  • 第1步,两个 goroutine 都到达通道,但哪个都没有开始执行发送或者接收。
  • 第 2步,左侧的 goroutine 将它的手伸进了通道,这模拟了向通道发送数据的行为。这时,这个 goroutine 会在通道中被锁住,直到交换完成。
  • 第 3 步,右侧的 goroutine 将它的手放入通道,这模拟了从通道里接收数据。这个 goroutine 一样也会在通道中被锁住,直到交换完成。
  • 第 4步和第 5 步,进行交换,并最终,在第6步,两个goroutine 都将它们的手从通道里拿出来,这模拟了被锁住的 goroutine 得到释放。两个 goroutine 现在都可以去做其他事情了。
有缓冲

  • 第1步,右侧的 goroutine 正在从通道接收一个值。
  • 第 2步,右侧的这个 goroutine独立完成了接收值的动作,而左侧的goroutine 正在发送一个新值到通道里。
  • 第 3 步,左侧的goroutine 还在向通道发送新值,而右侧的 goroutine 正在从通道接收另外一个值。这个步骤里的两个操作既不是同步的,也不会互相阻塞。
  • 最后,第4步,所有的发送和接收都完成,而通道里还有几个值,也有一些空间可以存更多的值。
  • 发生阻塞的情况:如果左侧放满了右侧还没取左侧会阻塞、或者右侧取空了左侧还没放右侧会阻塞

在上述定义中的实例是无缓冲channel,下面做有缓冲channel的示范

package mainimport ("fmt""time"
)func main() {//定义一个有缓冲channelc := make(chan int, 3)fmt.Println("len(c) = ", len(c), ",cap(c) = ", cap(c))//启动一个goroutine,向channel中发送数据go func() {defer fmt.Println("goroutine end")for i := 0; i < 3; i++ {c <- i //向channel中发送数据fmt.Println("goroutine start to send data:", i, "len(c)=", len(c), ",cap(c)=", cap(c))}}()time.Sleep(2 * time.Second)for i := 0; i < 3; i++ {num := <-c //从channel中接收数据fmt.Println("main goroutine receive data:", num, "len(c)=", len(c), ",cap(c)=", cap(c))}fmt.Println("main goroutine end")
}---------------------------------------------------------------------
PS D:\GoProject\firstGoProject> go run firstGoProject.go
len(c) =  0 ,cap(c) =  3
goroutine start to send data: 0 len(c)= 1 ,cap(c)= 3
goroutine start to send data: 1 len(c)= 2 ,cap(c)= 3
goroutine start to send data: 2 len(c)= 3 ,cap(c)= 3
goroutine end
main goroutine receive data: 0 len(c)= 2 ,cap(c)= 3
main goroutine receive data: 1 len(c)= 1 ,cap(c)= 3
main goroutine receive data: 2 len(c)= 0 ,cap(c)= 3
main goroutine end

上述可以看到协程发送完数据结束,主线程接收完数据结束。

如果发生或接收数据超过channel容量:

func main() {//定义一个有缓冲channelc := make(chan int, 3)fmt.Println("len(c) = ", len(c), ",cap(c) = ", cap(c))//启动一个goroutine,向channel中发送数据go func() {defer fmt.Println("goroutine end")for i := 0; i < 4; i++ {c <- i //向channel中发送数据fmt.Println("goroutine start to send data:", i, "len(c)=", len(c), ",cap(c)=", cap(c))}}()time.Sleep(2 * time.Second)for i := 0; i < 4; i++ {num := <-c //从channel中接收数据fmt.Println("main goroutine receive data:", num, "len(c)=", len(c), ",cap(c)=", cap(c))}fmt.Println("main goroutine end")
}--------------------------------------------------------------
PS D:\GoProject\firstGoProject> go run firstGoProject.go
len(c) =  0 ,cap(c) =  3
goroutine start to send data: 0 len(c)= 1 ,cap(c)= 3
goroutine start to send data: 1 len(c)= 2 ,cap(c)= 3
goroutine start to send data: 2 len(c)= 3 ,cap(c)= 3
main goroutine receive data: 0 len(c)= 3 ,cap(c)= 3
main goroutine receive data: 1 len(c)= 2 ,cap(c)= 3
main goroutine receive data: 2 len(c)= 1 ,cap(c)= 3
main goroutine receive data: 3 len(c)= 0 ,cap(c)= 3
main goroutine end

超过容量协程继续发送数据就会阻塞协程不会提前结束,而主线程已经结束了。(输出也会有不同的情况,不过相同的情况是协程会发生阻塞不会提前结束)

关闭channel

通过close()来关闭channel

正常关闭
package mainimport ("fmt"
)func main() {//定义一个channelc := make(chan int)//启动一个goroutine,向channel中发送数据go func() {for i := 0; i < 5; i++ {c <- i}//close可以关闭channelclose(c)}()for {//ok如果为true,表示channel没有关闭,如果为false表示channel已经关闭if data, ok := <-c; ok {fmt.Println(data)} else {break}}fmt.Println("main goroutine end")
}---------------------------------------------------------------------------PS D:\GoProject\firstGoProject> go run firstGoProject.go
0
1
2
3
4
main goroutine end
没有关闭

没有关闭会报死锁的错误,因为协程中已经向channel发送完毕数据了,主线程中依然还在等待数据导致主函数阻塞。(对应缓冲中提到过的阻塞情况)

func main() {//定义一个channelc := make(chan int)//启动一个goroutine,向channel中发送数据go func() {for i := 0; i < 5; i++ {c <- i}//close可以关闭channel//close(c)}()for {//ok如果为true,表示channel没有关闭,如果为false表示channel已经关闭if data, ok := <-c; ok {fmt.Println(data)} else {break}}fmt.Println("main goroutine end")
}---------------------------------------------------------------------------PS D:\GoProject\firstGoProject> go run firstGoProject.go
0
1
2
3
4
fatal error: all goroutines are asleep - deadlock!goroutine 1 [chan receive]:
main.main()D:/GoProject/firstGoProject/firstGoProject.go:23 +0xbd
exit status 2
注:
  •  channel不像文件一样需要经常去关闭,只有当你确实没有任何发送数据了,或者你想显式的结束range循环之类的,才去关闭channel;
  • 关闭channel后,无法向channel 再发送数据(引发 panic 错误后导致接收立即返回零值);
  • 关闭channel后,可以继续从channel接收数据;
  • 对于nil channel,无论收发都会被阻塞。

channel与range

func main() {//定义一个channelc := make(chan int)//启动一个goroutine,向channel中发送数据go func() {for i := 0; i < 5; i++ {c <- i}//close可以关闭channelclose(c)}()// for {// 	//ok如果为true,表示channel没有关闭,如果为false表示channel已经关闭// 	if data, ok := <-c; ok {// 		fmt.Println(data)// 	} else {// 		break// 	}// }//使用for range遍历channel,效果与注释掉的代码相同for data := range c {fmt.Println(data)}fmt.Println("main goroutine end")
}----------------------------------------------------------------------------PS D:\GoProject\firstGoProject> go run firstGoProject.go
0
1
2
3
4
main goroutine end

channel与select

单流程下一个go只能监控一个channel的状态,select可以完成监控多个channel的状态。

package mainimport ("fmt"
)func fibonacci(c, quit chan int) {x, y := 1, 1for {select {case c <- x://如果c可写,则case就会进来x = yy = x + ycase <-quit://如果quit可读,则case就会进来fmt.Println("quit")return}}
}func main() {c := make(chan int)quit := make(chan int)//sub gogo func() {for i := 0; i < 6; i++ {fmt.Println(<-c)}quit <- 0 //通知子go退出}()//main gofibonacci(c, quit)
}---------------------------------------------------------------------PS D:\GoProject\firstGoProject> go run firstGoProject.go
1
1
2
4
8
16
quit
select {case <- chanl://如果chan1成功读到数据,则进行该case处理语句case chan2 <- 1://如果成功向chan2写入数据,则进行该case处理语句default://如果上面都没有成功,则进入defau1t处理流程
}

http://www.dtcms.com/a/477533.html

相关文章:

  • 推三返一链动模式图解
  • 【人工智能与机器人研究】一种库坝系统水下成像探查有缆机器人系统设计模式
  • Qt---setAttribute设置控件或窗口的内部属性
  • 储能的“胜负手”:容量、策略与经济性如何平衡?
  • 蓝桥杯出局,少儿编程的价值祛魅时刻?
  • TensorFlow2 Python深度学习 - 使用TensorBoard可视化数据
  • wordpress忘记了密码忘记网站优化文章
  • 怎么看网站用哪个系统做的泰安集团网站建设方案
  • 在 openEuler 上为 LLVM/ASan 增强 wchar_t 字符串函数支持的开源贡献实践
  • git的命令
  • php mysql 网站源码北京网络营销培训
  • 科普:在分布式系统日志分析中的概念:`span`、`child_spans` 和 `trace`
  • 视频融合平台EasyCVR在智慧水利中的实战应用:构建全域感知与智能预警平台
  • 基于区块链的分布式密钥管理系统:构建去中心化、高可信的密码基础设施
  • 【Linux】进程控制(三) 自定义 Shell 命令行解释器的实现与进程协作实践
  • (论文速读)TRIP: 基于图像噪声先验的时间残差学习图像到视频生成模型详解
  • 查询建筑企业网站qq云 wordpress
  • MoonBit Pearls Vol.11:正则表达式引擎的两种实现方法:导数与 Thompson 虚拟机
  • 激光测距用高精度时间测量(TDC)电路——MS1205N
  • C语言⽂件操作讲解(总)
  • 计算时间复杂度
  • 【ComfyUI】视频替换背景
  • 天形、地气、人神:一种基于阴阳三元论的统一智能理论框架研究
  • 南通市经济开发区建设局网站网站建设企业网银e路通
  • Spring Boot 集成 WebSocket 的实战案例
  • 1-Ubuntu上创建脚本一键更换软件源
  • 【第2篇】nl2sql2025开发环境配置
  • 用 PyTorch 实现 MNIST 手写数字分类与训练损失曲线绘制
  • 获取 OpenAI API Key 全攻略:从入门配置Key到 GPT-5 Pro 集成与安全实战
  • 使用 Actix Web 构建 Web 应用