前言
Go语言最核心的特性是并发原生支持,通过Goroutine和Channel实现轻量级并发。Goroutine是由Go运行时管理的轻量级线程,创建成本极低(约2KB栈空间),而Channel则为Goroutine之间的通信提供了安全、高效的机制。本文深入剖析Goroutine的调度原理和Channel的使用技巧。
一、Goroutine基础
1.1 什么是Goroutine
Goroutine是Go运行时管理的轻量级线程,与传统线程相比:
| 特性 | 传统线程 | Goroutine |
|---|---|---|
| 创建成本 | 约1MB栈 | 约2KB栈(可动态增长) |
| 创建速度 | 较慢 | 极快 |
| 调度 | 内核级 | Go运行时调度(GMP模型) |
| 切换成本 | 用户态→内核态 | 用户态切换 |
1.2 创建Goroutine
import "fmt"
func hello(name string) {
fmt.Printf("Hello, %s!\n", name)
}
func main() {
// 创建一个新的Goroutine
go hello("Goroutine")
// 主Goroutine继续执行
fmt.Println("main函数执行中...")
// 等待一段时间让Goroutine执行
time.Sleep(time.Second)
fmt.Println("main函数结束")
}
1.3 Goroutine vs 线程
import (
"runtime"
"time"
)
func count() {
for i := 0; i < 5; i++ {
fmt.Printf("子Goroutine: %d\n", i)
time.Sleep(100 * time.Millisecond)
}
}
func main() {
fmt.Printf("初始Goroutine数量: %d\n", runtime.NumGoroutine())
go count()
go count()
fmt.Printf("启动后Goroutine数量: %d\n", runtime.NumGoroutine())
time.Sleep(500 * time.Millisecond)
fmt.Printf("结束时Goroutine数量: %d\n", runtime.NumGoroutine())
}
二、GMP调度模型
2.1 调度核心概念
Go的调度器采用GMP模型:
-
G(Goroutine):Go代码的逻辑单元
-
M(Machine/Thread):操作系统线程
-
P(Processor):执行上下文,有本地任务队列
全局任务队列
│
▼
┌─────────────┐
│ Scheduler │
└─────────────┘
│ │
┌────────────┘ └────────────┐
▼ ▼
┌──────────┐ ┌──────────┐
│ P0 │ │ P1 │
├──────────┤ ├──────────┤
│ G1 G2 │ │ G3 G4 │
└──────────┘ └──────────┘
│ │
▼ ▼
┌──────────┐ ┌──────────┐
│ M0 │ │ M1 │
│ 系统线程 │ │ 系统线程 │
└──────────┘ └──────────┘
2.2 Goroutine状态
┌─────────────┐
│ 创建 │
└──────┬──────┘
│
▼
┌──────────────────────────────┐
│ 可运行状态 │◄─────────┐
│ (等待P分配CPU时间片) │ │
└──────────────┬───────────────┘ │
│ │
┌─────────────┼─────────────┐ │
│ │ │ │
▼ ▼ ▼ │
┌──────────┐ ┌──────────┐ ┌──────────┐ │
│ 系统调用 │ │ 运行 │ │ 阻塞 │ │
│ (syscall)│ │ (running)│ │ (chan) │ │
└────┬─────┘ └────┬─────┘ └────┬─────┘ │
│ │ │ │
└──────────────┴──────────────┘ │
│ │
▼ │
┌─────────────┐ │
│ 结束 │──────────────────┘
│ (done) │
└─────────────┘
2.3 GOMAXPROCS
import (
"runtime"
"time"
)
func showCPU() {
fmt.Printf("GOMAXPROCS: %d\n", runtime.GOMAXPROCS(0))
fmt.Printf("CPU核数: %d\n", runtime.NumCPU())
}
func main() {
showCPU()
// 设置为1:单线程调度(调试并发问题)
runtime.GOMAXPROCS(1)
// 设置为CPU核数
runtime.GOMAXPROCS(runtime.NumCPU())
}
三、Channel基础
3.1 Channel的创建
// 无缓冲通道
ch1 := make(chan int)
// 有缓冲通道
ch2 := make(chan int, 10)
// 创建只读通道
var readCh <-chan int
// 创建只写通道
var writeCh chan<- int
3.2 Channel的数据结构
type hchan struct {
qcount uint // 队列中的数据数量
dataqsiz uint // 缓冲区大小(无缓冲为0)
buf unsafe.Pointer // 指向缓冲区的指针
elemsize uint16 // 元素大小
closed uint32 // 关闭标志
recvq waitq // 接收等待队列(阻塞的Goroutine)
sendq waitq // 发送等待队列
lock mutex // 保护整个channel的锁
}
3.3 图解Channel结构
无缓冲Channel:
┌─────────────────────────────────┐
│ hchan │
├─────────────────────────────────┤
│ qcount = 0 │
│ dataqsiz = 0 (无缓冲) │
│ buf = nil │
│ recvq ◄── [G1 waiting for recv] │
│ sendq ◄── [G2 waiting for send] │
└─────────────────────────────────┘
有缓冲Channel:
┌─────────────────────────────────┐
│ hchan │
├─────────────────────────────────┤
│ qcount = 3 │
│ dataqsiz = 10 │
│ buf ──────────────────────┐ │
│ recvq = empty │ │
│ sendq = empty │ │
└────────────────────────────┼────┘
│
▼
┌─────────────────┐
│ 缓冲区 │
├─────────────────┤
│ [0] [1] [2] ... │
└─────────────────┘
3.4 发送与接收
func main() {
// 创建通道
ch := make(chan int, 5)
// 发送数据
ch <- 1
ch <- 2
ch <- 3
// 接收数据
v1 := <-ch
v2 := <-ch
fmt.Printf("接收: %d, %d\n", v1, v2)
fmt.Printf("通道长度: %d, 容量: %d\n", len(ch), cap(ch))
}
四、Channel操作详解
4.1 发送、接收与关闭
func main() {
ch := make(chan int, 3)
// 发送
ch <- 1
ch <- 2
ch <- 3
// 接收
v := <-ch
fmt.Printf("收到: %d\n", v)
// 关闭通道(生产端关闭)
close(ch)
// 关闭后的接收:
// 1. 继续接收剩余数据
for v := range ch {
fmt.Printf("剩余数据: %d\n", v)
}
// 2. 已无数据时返回零值
v, ok := <-ch
fmt.Printf("通道已关闭, ok=%t, 值=%d\n", ok, v)
}
4.2 nil通道的行为
func main() {
var ch chan int // nil channel
// nil channel发送/接收会永久阻塞
// <-ch // 永久阻塞
// ch <- 1 // 永久阻塞
// 关闭nil channel会panic
// close(ch) // panic
fmt.Println("nil channel已创建")
}
4.3 单向通道
// 生产者函数:只写通道
func producer(ch chan<- int) {
for i := 0; i < 5; i++ {
ch <- i
}
close(ch) // 关闭通道
}
// 消费者函数:只读通道
func consumer(ch <-chan int) {
for v := range ch {
fmt.Printf("消费: %d\n", v)
}
}
func main() {
ch := make(chan int)
go producer(ch)
consumer(ch)
}
五、Select语句
5.1 select基础
func main() {
ch1 := make(chan int, 1)
ch2 := make(chan int, 1)
ch1 <- 1
// ch2 <- 2 // 不发送,让ch2阻塞
select {
case v := <-ch1:
fmt.Printf("从ch1收到: %d\n", v)
case v := <-ch2:
fmt.Printf("从ch2收到: %d\n", v)
default:
fmt.Println("两个通道都阻塞,执行default")
}
}
5.2 多通道监听
func main() {
ch1 := make(chan int)
ch2 := make(chan string)
// 启动两个Goroutine
go func() {
time.Sleep(1 * time.Second)
ch1 <- 42
}()
go func() {
time.Sleep(500 * time.Millisecond)
ch2 <- "hello"
}()
// 同时监听两个通道
for i := 0; i < 2; i++ {
select {
case v := <-ch1:
fmt.Printf("ch1: %d\n", v)
case v := <-ch2:
fmt.Printf("ch2: %s\n", v)
}
}
}
5.3 超时处理
func main() {
ch := make(chan int)
// 启动一个Goroutine,2秒后发送数据
go func() {
time.Sleep(2 * time.Second)
ch <- 100
}()
// 设置1秒超时
select {
case v := <-ch:
fmt.Printf("收到: %d\n", v)
case <-time.After(1 * time.Second):
fmt.Println("超时!")
}
}
5.4 nil通道在select中
func main() {
var ch1 chan int // nil channel
ch2 := make(chan int)
// ch1是nil,不会被选中
select {
case v := <-ch1: // 永远阻塞,不会执行
fmt.Printf("ch1: %d\n", v)
case v := <-ch2:
fmt.Printf("ch2: %d\n", v)
default:
fmt.Println("default")
}
}
六、并发模式
6.1 生产者-消费者
func producer(ch chan<- int) {
for i := 0; i < 10; i++ {
ch <- i
time.Sleep(100 * time.Millisecond)
}
close(ch)
}
func consumer(id int, ch <-chan int, wg *sync.WaitGroup) {
defer wg.Done()
for v := range ch {
fmt.Printf("消费者%d: %d\n", id, v)
}
}
func main() {
ch := make(chan int, 5)
var wg sync.WaitGroup
// 1个生产者
go producer(ch)
// 3个消费者
for i := 1; i <= 3; i++ {
wg.Add(1)
go consumer(i, ch, &wg)
}
wg.Wait()
fmt.Println("完成")
}
6.2 Fanout-Fanin模式
func main() {
// 输入通道
input := make(chan int, 100)
// 启动多个worker
worker := func(id int, in <-chan int) <-chan int {
out := make(chan int)
go func() {
defer close(out)
for v := range in {
out <- v * v
}
}()
return out
}
// Fan-out: 启动3个worker
outputs := []<-chan int{
worker(1, input),
worker(2, input),
worker(3, input),
}
// Fan-in: 合并多个通道
final := merge(outputs...)
// 发送数据
go func() {
for i := 1; i <= 10; i++ {
input <- i
}
close(input)
}()
// 收集结果
for v := range final {
fmt.Println(v)
}
}
// 合并多个通道
func merge(channels ...<-chan int) <-chan int {
out := make(chan int)
var wg sync.WaitGroup
for _, ch := range channels {
wg.Add(1)
go func(c <-chan int) {
defer wg.Done()
for v := range c {
out <- v
}
}(ch)
}
go func() {
wg.Wait()
close(out)
}()
return out
}
6.3 管道模式
func generate(nums ...int) <-chan int {
out := make(chan int)
go func() {
for _, n := range nums {
out <- n
}
close(out)
}()
return out
}
func square(in <-chan int) <-chan int {
out := make(chan int)
go func() {
for v := range in {
out <- v * v
}
close(out)
}()
return out
}
func filterOdd(in <-chan int) <-chan int {
out := make(chan int)
go func() {
for v := range in {
if v%2 == 0 {
out <- v
}
}
close(out)
}()
return out
}
func main() {
// 构建管道: generate -> square -> filterOdd -> print
pipeline := filterOdd(square(generate(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)))
for v := range pipeline {
fmt.Println(v)
}
}
6.4 Context取消
func longRunningTask(ctx context.Context) error {
for {
select {
case <-ctx.Done():
return ctx.Err()
default:
// 执行任务
fmt.Println("执行中...")
time.Sleep(500 * time.Millisecond)
}
}
}
func main() {
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
defer cancel()
err := longRunningTask(ctx)
if err != nil {
fmt.Printf("任务取消: %v\n", err)
}
}
七、常见面试题
Q1: 无缓冲vs有缓冲Channel的区别
// 无缓冲Channel
ch1 := make(chan int)
// 发送会阻塞直到有人接收
// 接收会阻塞直到有人发送
// 有缓冲Channel
ch2 := make(chan int, 3)
// 发送只在缓冲区满时阻塞
// 接收只在缓冲区空时阻塞
Q2: 发送和接收的阻塞情况
func main() {
// 1. 无缓冲Channel
ch := make(chan int)
// 以下会死锁:
// ch <- 1 // 发送阻塞,没有接收者
// 2. 有缓冲Channel
ch2 := make(chan int, 1)
ch2 <- 1 // 不阻塞,缓冲区有空间
ch2 <- 2 // 阻塞,缓冲区满
}
Q3: Select的执行顺序
func main() {
ch := make(chan int, 1)
ch <- 1
// 如果多个case同时就绪,随机选择一个
select {
case <-ch:
fmt.Println("case1")
case <-ch:
fmt.Println("case2")
default:
fmt.Println("default")
}
}
总结
-
Goroutine:Go运行时管理的轻量级线程,约2KB栈空间
-
GMP模型:G(Goroutine)- M(Machine)- P(Processor)调度
-
Channel:Goroutine通信机制,类型安全
-
Select:多通道 multiplexing,支持超时和default分支
-
并发模式:生产者-消费者、Fanout-Fanin、Pipeline等
最佳实践:
-
使用sync.WaitGroup管理多Goroutine等待
-
使用context传递取消信号
-
有缓冲Channel用于解耦生产者和消费者
-
避免在Channel上发送nil值
-
关闭Channel意味着一生产者完成
💡 下一篇文章我们将深入讲解Go语言的Context机制,敬请期待!