找回密码
立即注册
搜索
热搜: Java Python Linux Go
发回帖 发新帖

2408

积分

0

好友

312

主题
发表于 5 天前 | 查看: 21| 回复: 0

goroutine与channel的通信示意图

在Go语言中,goroutine是实现并发执行的基本单元,它是一种极其轻量级的执行线程。值得注意的是,程序的主函数 main() 本身也运行在一个goroutine中,这是Go程序的默认执行上下文。

在同一个goroutine内部,代码会严格地按照顺序从上到下执行。如果遇到了一个执行非常缓慢的操作(例如耗时的I/O或复杂计算),这个goroutine就会被阻塞,其后的所有操作都必须等待,这就像单车道发生了交通堵塞,后面的车辆只能干等。

func slowFunc() {
    fmt.Println("耗时操作开始")
    // 暂停 goroutine 两秒,模拟耗时的操作
    time.Sleep(time.Second * 2)
    fmt.Println("耗时操作结束")
}

func main() {
    fmt.Println("主函数开始")
    slowFunc()
    fmt.Println("主函数结束")
}

顺序执行代码示例

顺序执行输出结果

这种阻塞无疑会严重拖累程序的整体运行效率。为了提升代码性能,一个常见的策略是将耗时操作放入一个独立的 goroutine 中执行。这样,主goroutine(即主函数)就不会被阻塞,可以继续执行后续的代码。这好比在多车道的高速公路上,即使一条车道发生事故,其他车道上的车辆依然可以正常通行(前提是车道间有隔离带)。

在函数调用前加上 go 关键字,就能让这个函数在一个全新的、独立的goroutine中异步执行。

func slowFunc() {
    fmt.Println("耗时操作开始")
    time.Sleep(time.Second * 2)
    fmt.Println("耗时操作结束")
}

func main() {
    fmt.Println("主函数开始")
    // 把慢函数放在新 goroutine 中执行
    // 不要在主函数中挡道
    go slowFunc()
    fmt.Println("在主函数中干点别的")
    fmt.Println("主函数结束")
}

使用goroutine异步执行代码

异步执行输出结果

从执行结果可以清楚地看到,主函数果然没有等待耗时的 slowFunc 函数,而是直接执行了后续的打印语句,速度大大提升。

然而,这带来了一个新问题:主函数运行速度是快了,但那个被“放飞”的耗时操作却两手一摊,无奈地表示:“我还没执行完呢,你怎么就结束了?” 主函数可以追求速度,但不能对其它goroutine的生死置之不理。在主函数结束前,我们最好能等待一下其它关键的goroutine完成任务。

这时,就需要在两个goroutine之间建立通信机制,而Go语言提供的解决方案就是 channel(通道)。我们来改造一下代码,让耗时操作结束后,能通过channel通知主函数:“老大,我的任务完成了,你可以安心结束了”。

func slowFunc(c chan string) {
    fmt.Println("耗时操作开始")
    time.Sleep(time.Second * 2)
    fmt.Println("耗时操作结束")

    // 向通道中发送信息
    c <- "老大,可以结束了"
}

func main() {
    // 创建一个 channel,只能传递字符串
    c := make(chan string)

    fmt.Println("主函数开始")
    go slowFunc(c)
    fmt.Println("在主函数中干点别的")

    // 从 channel 中读取信息
    msg := <-c
    fmt.Printf("收到慢函数的信息:%v\n", msg)

    fmt.Println("主函数结束")
}

使用channel进行goroutine间通信的代码

channel通信输出结果

现在,主函数在打印完“在主函数中干点别的”之后,执行到 msg := <-c 时会主动等待,直到从channel中接收到 slowFunc 发来的消息,才会继续执行并最终结束。程序的逻辑变得完整且可控。

channel的基本用法可以简单总结为以下几点:

  1. 使用 make(chan T) 创建通道,其中 T 指定了通道所能传递消息的数据类型。
  2. 发送方使用 c <- data 语法,将数据 data 写入通道实例 c
  3. 接收方使用 variable := <-c 语法,从通道实例 c 中读取数据并赋值给变量。
  4. 接收操作是阻塞的:如果通道中没有数据,接收方的goroutine会一直等待,直到有数据可读或通道被关闭。
  5. 发送方可以通过 close(c) 函数来主动关闭通道,通知接收方不再有数据发送。

那么,如果发送方提前关闭了通道,会发生什么?下面的代码展示了这种场景及其后果。

func slowFunc(c chan string) {
    fmt.Println("耗时操作开始")
    close(c) // 慢操作主动结束 channel
    time.Sleep(time.Second * 2)
    fmt.Println("耗时操作结束")
}

func main() {
    c := make(chan string)

    fmt.Println("主函数开始")
    go slowFunc(c)
    fmt.Println("在主函数中干点别的")

    msg := <-c
    fmt.Printf("收到慢函数的信息:%v\n", msg)

    fmt.Println("主函数结束")
}

发送方提前关闭channel的代码示例

提前关闭channel的输出结果

可以看到,一旦主函数从已关闭的channel中读取到零值(对于字符串是空字符串),它便不再等待 slowFunc 中那个漫长的 Sleep 操作,直接宣告程序结束。而 slowFunc 这个goroutine在休眠结束后,虽然打印了“耗时操作结束”,但此时主进程可能早已退出。

goroutine与channel共同构成了Go语言强大且优雅的并发编程基石。goroutine的启动开销极小,初始栈大小仅为2KB,并且会根据需要自动扩容。一个简单的计算可以让我们感受到它的轻量级:如果你的电脑内存是4GB,那么理论上可以创建的goroutine数量约为 4GB / 2KB ≈ 200万 个。

当然,实际运行中受限于CPU调度、系统资源等因素,不可能真正达到这个数字,但支撑起十万、百万级别的并发任务对于Go来说并非难事。这不禁让人联想到孙悟空“拔一根毫毛,吹出猴万个”的神通,而Go语言在“吹”出并发执行体这方面,潜力或许比猴哥还要惊人。

掌握goroutine和channel,是深入Go并发编程世界的关键一步。如果你想了解更多关于Go语言或其他后端架构的深度内容,欢迎来到 云栈社区 与更多开发者一同交流探讨。

参考资料




上一篇:梯度下降选择Offer?算法工程师如何走出职业决策的局部最优
下一篇:Windows AI部署全攻略:5种适合初学者的实战方案(含Python/云服务/容器化)
您需要登录后才可以回帖 登录 | 立即注册

手机版|小黑屋|网站地图|云栈社区 ( 苏ICP备2022046150号-2 )

GMT+8, 2026-1-24 02:49 , Processed in 0.306550 second(s), 41 queries , Gzip On.

Powered by Discuz! X3.5

© 2025-2026 云栈社区.

快速回复 返回顶部 返回列表