
💡 核心概念: Go 语言的并发(Concurrency)是其最大的杀手锏。
1. Goroutine (协程):轻量级的线程,也就是“影分身”。
2. Channel (通道):协程之间通信的管道,也就是“传音入密”。
3. Sync (同步):确保分身们不会打架(数据竞争)。
在调用函数前加一个 go 关键字,就能瞬间启动一个分身去执行任务,而你自己(主线程)可以继续往下走,不用傻等。
package main
import (
"fmt"
"time"
)
func SweepFloor() {
for i := 0; i < 5; i++ {
fmt.Println("🧹 分身正在扫地...", i)
time.Sleep(100 * time.Millisecond) // 模拟干活耗时
}
}
func main() {
// 启动一个分身去扫地
go SweepFloor()
// 主身继续干别的
for i := 0; i < 5; i++ {
fmt.Println("🖌️ 主身正在画画...", i)
time.Sleep(100 * time.Millisecond)
}
}

用 time.Sleep 等待是不靠谱的。正规的做法是用 sync.WaitGroup 来点名。
import "sync"
var wg sync.WaitGroup // 定义一个计数器
func WashDishes() {
defer wg.Done() // 干完活了,计数器减 1
fmt.Println("🍽️ 洗碗完成!")
}
func main() {
wg.Add(1) // 计数器加 1,表示有一个活要干
go WashDishes()
fmt.Println("主身在喝茶等待...")
wg.Wait() // 阻塞在这里,直到计数器归零
fmt.Println("所有活都干完了,收工!")
}
影分身之间怎么交换情报?不要通过共享内存(比如全局变量)来通信,而要通过通信来共享内存。这就是 Channel。
func main() {
// 创建一个传送字符串的通道
// make(chan 数据类型)
ch := make(chan string)
// 启动分身发送消息
go func() {
fmt.Println("分身:准备发送情书...")
ch <- "秋香姐,我爱你!" // 把消息塞进管道
fmt.Println("分身:发送完毕")
}()
// 主身接收消息
msg := <-ch // 阻塞等待,直到管道里有东西出来
fmt.Println("主身收到消息:", msg)
}

如果同时有好几个消息来源,怎么处理?Go 提供了 select 关键字,它可以同时监听多个通道。
func main() {
qiuxiang := make(chan string)
shiliu := make(chan string)
go func() { time.Sleep(1 * time.Second); qiuxiang <- "公子,我是秋香" }()
go func() { time.Sleep(2 * time.Second); shiliu <- "9527,我是石榴姐" }()
// 谁的消息先到,就处理谁
for i := 0; i < 2; i++ {
select {
case msg1 := <-qiuxiang:
fmt.Println("😍 收到秋香的消息:", msg1)
case msg2 := <-shiliu:
fmt.Println("😱 收到石榴的消息:", msg2)
}
}
}
如果多个分身同时抢着用厨房(修改同一个变量),就会乱套(数据竞争)。这时需要加锁。
import "sync"
var (
rice = 0
lock sync.Mutex // 厨房门锁
)
func AddRice() {
lock.Lock() // 进门先上锁
defer lock.Unlock() // 出门解锁
rice++
}
func main() {
var wg sync.WaitGroup
for i := 0; i < 1000; i++ {
wg.Add(1)
go func() {
defer wg.Done()
AddRice()
}()
}
wg.Wait()
fmt.Println("米缸里的米粒数:", rice) // 如果不加锁,这里可能不到 1000
}
如果皇上(主程序)下旨说“不用干了,退下”,所有分身必须立刻停止。Go 提供了 context 来实现这种超时控制和取消机制。
import (
"context"
"time"
)
func main() {
// 创建一个带超时的上下文,3秒后自动取消
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
defer cancel()
go func(ctx context.Context) {
select {
case <-time.After(5 * time.Second):
fmt.Println("任务完成!")
case <-ctx.Done(): // 监听取消信号
fmt.Println("🛑 收到圣旨:时间到,任务取消!")
}
}(ctx)
time.Sleep(4 * time.Second)
}
启动一个 goroutine 向通道发送数字 9527,主函数接收并打印。
package main
import "fmt"
func main() {
c := make(chan int)
// 填空:启动一个 goroutine
______ func() {
c <- 9527
}()
// 接收数据
fmt.Println(<-c)
}
任务: 启动协程的关键字是什么?
答案: go
解析: go 关键字用于启动一个新的 Goroutine。这是 Go 语言并发编程的基石。