如何在Golang中创建与使用goroutine_Golang goroutine的创建与同步方法

9次阅读

启动 goroutine 需用 go 关键字加函数调用表达式,难点在于同步等待与避免数据竞争;必须用 sync.waitgroup 而非 time.sleep 等待结束,且 add 必须在 go 前调用,done 在 goroutine 内 defer 调用。

如何在 Golang 中创建与使用 goroutine_Golang goroutine 的创建与同步方法

Go 语言中启动 goroutine 只需在函数调用前加 go 关键字,但真正难点不在“怎么开”,而在“怎么等它结束”和“怎么避免数据竞争”。不处理同步,main 函数很可能在 goroutine 执行前就退出。

goroutine 的创建:不是函数调用,是并发调度请求

go 后面必须跟一个函数调用表达式(哪怕匿名),不能是变量、方法名或带括号但无参数的函数名(除非你真想立即执行)。

常见错误写法:

go myFunc    // ❌ 编译错误:缺少括号,这不是调用 go myFunc()  // ✅ 正确:立即启动 goroutine 执行 myFunc go func() {……}()  // ✅ 匿名函数也需加 () 才会执行 

注意:go 是轻量级的——它不保证立刻执行,只是把任务提交给 Go 运行时调度器;实际何时运行、在哪一个 OS 线程上跑,由 runtime 决定。

立即学习 go 语言免费学习笔记(深入)”;

等待 goroutine 结束:别用 sleep,用 sync.WaitGroup

time.Sleep“碰运气”等 goroutine 完成,是典型反模式。正确做法是用 sync.WaitGroup 主动计数。

关键点:

  • WaitGroup.Add(1) 必须在 go 语句之前调用(否则可能 Wait() 已返回,而 Add 还没执行)
  • defer wg.Done() 要放在 goroutine 内部函数开头,确保无论是否 panic 都能通知完成
  • wg.Wait() 会阻塞,直到所有 Done() 被调用

示例:

var wg sync.WaitGroup for i := 0; i < 3; i++ {wg.Add(1)     go func(id int) {defer wg.Done()         fmt.Printf("goroutine %d donen", id)     }(i) } wg.Wait() // main 在这里等待全部完成 

共享数据安全:不要靠“我保证不同时读写”,要用 channel 或 mutex

多个 goroutine 直接读写同一变量(如 counter++)是未定义行为,go run -race 会立刻报出 data race。

两种主流解法:

  • channel 传递所有权:适合生产者 - 消费者模型,天然串行化访问。例如用 chan int 收集结果,而不是让多个 goroutine 去改同一个 slice
  • sync.Mutexsync.RWMutex:适合需要频繁读、偶尔写的场景。注意 Lock()/Unlock() 必须成对,且锁粒度不宜过大(比如别在循环里反复 Lock/Unlock)

错例(竞态):

var counter int for i := 0; i < 10; i++ {go func() {counter++ // ❌ 多个 goroutine 并发写 counter}()}

goroutine 泄漏:忘记接收 channel 或没设超时

goroutine 不会自动回收。如果它在 ch 或 <code> 上永久阻塞(比如 channel 没人收、没人发),它就永远卡住,占用内存和栈空间。

预防手段:

  • 带缓冲的 channel 要配好容量,避免发送方因缓冲满而阻塞
  • 从 channel 接收时,优先考虑 select + defaulttime.After 做超时控制
  • 使用 context.Context 传递取消信号,尤其在调用外部服务或长时间 IO 时

典型泄漏场景:

ch := make(chan int) go func() {     ch <- 42 // 如果没人从 ch 读,这个 goroutine 永远卡在这里}() // 忘记 <-ch —— 泄漏发生 

goroutine 本身开销小,但同步逻辑写错带来的问题(竞态、死锁、泄漏)往往比性能损耗更难排查。与其纠结“要不要开 goroutine”,不如先想清楚“怎么让它安全地开始、通信、结束”。

text=ZqhQzanResources