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

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()被调用
示例:
AJAX即“Asynchronous Javascript And XML”(异步JavaScript和XML),是指一种创建交互式网页应用的网页开发技术。它不是新的编程语言,而是一种使用现有标准的新方法,最大的优点是在不重新加载整个页面的情况下,可以与服务器交换数据并更新部分网页内容,不需要任何浏览器插件,但需要用户允许JavaScript在浏览器上执行。《php中级教程之ajax技术》带你快速
var wg sync.WaitGroup
for i := 0; i < 3; i++ {
wg.Add(1)
go func(id int) {
defer wg.Done()
fmt.Printf("goroutine %d done\n", id)
}(i)
}
wg.Wait() // main 在这里等待全部完成共享数据安全:不要靠“我保证不同时读写”,要用 channel 或 mutex
多个 goroutine 直接读写同一变量(如 counter++)是未定义行为,go run -race 会立刻报出 data race。
两种主流解法:
- 用
channel传递所有权:适合生产者-消费者模型,天然串行化访问。例如用chan int收集结果,而不是让多个 goroutine 去改同一个 slice - 用
sync.Mutex或sync.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+default或time.After做超时控制 - 使用
context.Context传递取消信号,尤其在调用外部服务或长时间 IO 时
典型泄漏场景:
ch := make(chan int)
go func() {
ch <- 42 // 如果没人从 ch 读,这个 goroutine 永远卡在这里
}()
// 忘记 <-ch —— 泄漏发生goroutine 本身开销小,但同步逻辑写错带来的问题(竞态、死锁、泄漏)往往比性能损耗更难排查。与其纠结“要不要开 goroutine”,不如先想清楚“怎么让它安全地开始、通信、结束”。









