Golang的并发模型以Goroutine(轻量级线程)和Channel(通信管道)为核心,基于**CSP(Communicating Sequential Processes,通信顺序进程)**理论设计,强调“通过通信来共享内存”而非“通过共享内存来通信”,从根本上降低了并发编程的复杂性。
Goroutine是Go语言的并发基石,由Go运行时(Runtime)在用户态管理,创建、销毁及切换的开销远小于操作系统线程(通常仅需几KB栈空间,初始栈可动态扩容)。通过go
关键字即可启动一个Goroutine,例如:
go func() {
fmt.Println("Hello from Goroutine")
}()
Goroutine的调度采用M:N模型(M个Goroutine映射到N个操作系统线程),通过**工作窃取(Work Stealing)**策略平衡负载:空闲的逻辑处理器(P)会从其他P的本地队列中偷取Goroutine执行,充分利用多核CPU资源。
Channel是Goroutine之间传递数据的同步原语,分为无缓冲(同步阻塞)和有缓冲(异步非阻塞)两种类型:
ch := make(chan int) // 无缓冲Channel
go func() { ch <- 42 }() // 发送阻塞,直到主Goroutine接收
fmt.Println(<-ch) // 接收阻塞,直到Goroutine发送
bufCh := make(chan int, 2) // 缓冲区大小为2
bufCh <- 1 // 不阻塞
bufCh <- 2 // 不阻塞
fmt.Println(<-bufCh) // 接收1
fmt.Println(<-bufCh) // 接收2
Channel遵循“先进先出(FIFO)”原则,关闭后不可发送数据,但可继续接收剩余数据。
尽管Channel能解决大部分并发问题,但在共享内存场景下,仍需借助sync包中的同步原语保证数据一致性:
sync.WaitGroup
通过计数器实现同步,核心方法包括:
Add(delta int)
:增加计数器(需在启动Goroutine前调用);Done()
:减少计数器(通常在defer
中调用);Wait()
:阻塞主Goroutine,直到计数器归零。var wg sync.WaitGroup
for i := 0; i < 5; i++ {
wg.Add(1) // 增加计数器
go func(i int) {
defer wg.Done() // 完成后减少计数器
fmt.Println(i)
}(i)
}
wg.Wait() // 等待所有Goroutine完成
Lock()
和Unlock()
方法实现排他访问,防止多个Goroutine同时修改共享资源。例如:var (
counter int
mu sync.Mutex
)
func increment() {
mu.Lock() // 加锁
defer mu.Unlock() // 解锁
counter++
}
RLock()
/RUnlock()
),但写入时排他(Lock()
/Unlock()
)。sync.Once
通过Do(f func())
方法保证传入的函数仅执行一次,适用于初始化操作(如配置加载)。例如:
var once sync.Once
func setup() {
once.Do(func() {
fmt.Println("Initialization done") // 仅执行一次
})
}
context
包用于跨Goroutine传递取消信号、超时或截止时间,避免Goroutine泄漏。常用方法:
WithCancel
:创建可取消的Context;WithTimeout
:创建带超时的Context;Done()
:返回一个Channel,当Context被取消或超时时关闭。ctx, cancel := context.WithCancel(context.Background())
go func(ctx context.Context) {
select {
case <-ctx.Done(): // 收到取消信号
fmt.Println("Goroutine stopped")
return
case <-time.After(2 * time.Second):
fmt.Println("Work done")
}
}(ctx)
time.Sleep(1 * time.Second)
cancel() // 手动取消
time.Sleep(1 * time.Second) // 等待Goroutine退出
select
语句用于监听多个Channel的操作,类似“IO多路复用”,可处理多个Channel的发送/接收事件。例如:
select {
case msg1 := <-ch1:
fmt.Println("Received:", msg1)
case msg2 := <-ch2:
fmt.Println("Received:", msg2)
case <-time.After(1 * time.Second): // 超时控制
fmt.Println("Timeout")
}
Go的并发调度器采用M:N模型,核心组件包括:
调度策略的关键点:
在实际开发中,Golang的并发模型常结合以下模式提升效率:
通过缓冲Channel模拟线程池,限制同时运行的Goroutine数量,避免资源耗尽。例如:
func worker(id int, jobs <-chan int, results chan<- int) {
for j := range jobs {
fmt.Printf("Worker %d processing job %d\n", id, j)
results <- j * 2 // 模拟耗时操作
}
}
func main() {
jobs := make(chan int, 100)
results := make(chan int, 100)
// 启动3个Worker
for w := 1; w <= 3; w++ {
go worker(w, jobs, results)
}
// 发送9个任务
for j := 1; j <= 9; j++ {
jobs <- j
}
close(jobs) // 关闭任务Channel
// 收集结果
for a := 1; a <= 9; a++ {
<-results
}
}
通过多个Channel串联多个处理阶段(如数据采集→处理→存储),每个阶段由独立的Goroutine执行,实现流水线并发。例如:
func stage1(in <-chan int) <-chan int {
out := make(chan int)
go func() {
for n := range in {
out <- n * 2 // 第一阶段:乘以2
}
close(out)
}()
return out
}
func stage2(in <-chan int) <-chan int {
out := make(chan int)
go func() {
for n := range in {
out <- n + 1 // 第二阶段:加1
}
close(out)
}()
return out
}
func main() {
in := make(chan int, 10)
go func() {
for i := 1; i <= 5; i++ {
in <- i
}
close(in)
}()
// 串联两个Stage
out1 := stage1(in)
out2 := stage2(out1)
// 输出最终结果
for n := range out2 {
fmt.Println(n) // 输出:3, 5, 7, 9, 11
}
}
-race
标志检测(如go run -race main.go
),通过sync.Mutex
或Channel
避免共享内存的并发访问;context
取消、done
Channel通知),避免因未释放资源导致内存泄漏;