Go语言实战案例:使用 Goroutine 并发打印

在本篇文章中,我们将从最基础的 Goroutine 并发模型入手,逐步掌握 Go 语言中并发控制的核心思想。通过一个简单的"并发打印任务"案例,掌握 Goroutine 的使用、输出顺序的控制、主协程退出时机等关键知识。


一、为什么学习 Goroutine?

Goroutine 是 Go 语言最核心的并发原语。相比于线程,它更加轻量:

  • 启动成本极低(大约只需 2KB 内存)
  • 数量可以轻松达到上万甚至百万级
  • 使用非常简单,只需一个 go 关键字

Go 的并发理念是:不要通过共享内存来通信,而要通过通信来共享内存。

掌握 Goroutine,是理解 Go 并发模型的第一步。


二、实战目标

我们将实现一个小项目:

  1. 启动多个 Goroutine 并发打印任务信息
  2. 每个任务打印固定内容
  3. 确保主程序等待所有任务完成
  4. 引入 sync.WaitGroup 实现同步控制

三、基础版本:启动多个 Goroutine

下面是最简单的 Goroutine 并发打印实现:

go 复制代码
package main

import (
    "fmt"
    "time"
)

func printTask(id int) {
    fmt.Printf("任务 %d 正在执行\n", id)
}

func main() {
    for i := 1; i <= 5; i++ {
        go printTask(i)
    }

    // 给协程一点时间完成(不推荐的方式)
    time.Sleep(time.Second)
    fmt.Println("主程序结束")
}

运行结果(示例):

复制代码
任务 2 正在执行
任务 4 正在执行
任务 1 正在执行
任务 3 正在执行
任务 5 正在执行
主程序结束

你会注意到,输出顺序可能与代码中的顺序不同,这是并发执行的结果。


四、问题:主程序过早退出

在上面例子中我们使用了 time.Sleep 来"凑时间",这是一种不可靠且危险的方式。如果任务执行时间更长,就会被主程序强制退出。

为了解决这个问题,我们引入 sync.WaitGroup。


五、进阶版本:使用 WaitGroup 等待所有 Goroutine 完成

go 复制代码
package main

import (
    "fmt"
    "sync"
    "time"
)

// 模拟执行任务
func printTask(id int, wg *sync.WaitGroup) {
    defer wg.Done() // 标记一个任务完成
    fmt.Printf("任务 %d 开始执行\n", id)
    time.Sleep(time.Duration(id) * 200 * time.Millisecond) // 模拟不同任务耗时
    fmt.Printf("任务 %d 执行完毕\n", id)
}

func main() {
    var wg sync.WaitGroup

    taskCount := 5
    for i := 1; i <= taskCount; i++ {
        wg.Add(1) // 增加一个任务
        go printTask(i, &wg)
    }

    wg.Wait() // 等待所有任务完成
    fmt.Println("所有任务完成,主程序退出")
}

输出示例:

复制代码
任务 1 开始执行
任务 2 开始执行
任务 3 开始执行
任务 4 开始执行
任务 5 开始执行
任务 1 执行完毕
任务 2 执行完毕
任务 3 执行完毕
任务 4 执行完毕
任务 5 执行完毕
所有任务完成,主程序退出

六、重点知识点解析

1. go 关键字

只需在函数前加 go,即可将该函数交由调度器异步执行,立即返回。

2. sync.WaitGroup

  • wg.Add(n):添加 n 个任务
  • wg.Done():在任务完成后调用
  • wg.Wait():阻塞主线程,直到所有任务完成

3. defer 用法

使用 defer wg.Done() 可以确保即使函数中途返回也能正确减计数。


七、延伸思考

  • 若有 10万个任务要并发执行,Goroutine 是否能承载?(答案是:可以,但建议使用并发控制池)
  • 如果某个任务失败,是否能提前取消其他任务?(答案是:可以结合 context 实现)

八、真实应用场景举例

  • 批量调用多个接口(如:并发请求天气接口、发邮件)
  • 批量处理文件(压缩、上传、转码)
  • 并发爬虫(抓取多个页面)
  • 消息队列并发消费任务

九、小结

本文通过"并发打印任务"这一简单示例,向你展示了 Go 并发编程的基本能力:

  • 启动 Goroutine 的方法
  • 并发任务间的非确定性
  • 如何使用 WaitGroup 来保证主程序的正确退出
  • Goroutine 的轻量特性,适合高并发场景

并发是 Go 的灵魂,控制好协程的生命周期和调度能力,将直接影响系统的健壮性与性能。

相关推荐
我不会起名字32229 分钟前
Redis 从 0 到 1(十三):分布式锁被误删、锁不住?Go 无 Lua 实现 3 级防御
redis·go·分布式锁·watch·setnx
鼓掌MVP21 小时前
打工人战绩翻译机:我把自己"修bug"的日常喂给 TRAE,它吐出来一份能让领导点头的周报
trae
hourinX21 小时前
从 v1 到 v2,我重新拆了头像生成的整条链路—— gdenticon 重构日志(二)
go
weixin_449229381 天前
Skill应用11:详细设计自动生成|强制“文字先行”,杜绝AI只堆代码不讲设计
人工智能·agent·架构设计·详细设计·trae·skill应用
FungLeo1 天前
成为全栈·Go 后端篇·分类树与标签关系:目录变更为何需要协调
golang·go·gorm·数据库事务·分类树·成为全栈
FungLeo1 天前
成为全栈·Go 后端篇·文章查询与展示投影:安全分页、排序和软删除
golang·go·gorm·分页查询·成为全栈·sql安全
KevinAshen672 天前
用 Go 修复旧下载工具:从接口 403 到凭证截断的完整复盘
go·github
FungLeo3 天前
成为全栈·Go 后端篇·跨语言认证兼容:Node 与 Go 如何互认 JWT 和密码
golang·go·jwt·身份认证·成为全栈·bcrypt
FungLeo3 天前
成为全栈·Go 后端篇·重写之前先锁基线:契约、Node 版本与行为清单
golang·node.js·go·openapi·契约测试·成为全栈
小满zs3 天前
Go语言第十七章(反射)
后端·go