go 语言使用 goroutine 而非 os 线程,其轻量、用户态调度、动态栈和 channel 协作机制要求摒弃线程思维;runtime.lockosthread() 仅限特殊场景,gomaxprocs 默认即最优,channel 缓冲区重在语义而非性能,并发控制应依赖 waitgroup、context 或信号量而非 sleep。

Go 语言没有传统意义上的“多线程编程”接口,go 启动的是 goroutine,不是 OS 线程;真正需要控制线程级行为时,必须通过 runtime.LockOSThread() 显式绑定,但绝大多数业务场景不该碰它。
goroutine 不是线程,别用线程思维写 Go
很多人刚从 Java/Python 转来,下意识把 go f() 当作“启一个线程”,然后去想锁、线程池大小、上下文切换开销——这会直接带偏设计方向。
- goroutine 栈初始仅 2KB,可动态伸缩;OS 线程栈通常 1MB+,数量受限
- goroutine 调度在用户态完成,
select/chan阻塞不会占用 OS 线程 -
runtime.GOMAXPROCS(n)控制的是 P(Processor)数量,不是线程数;默认值已是 CPU 核心数,除非有明确理由否则不要改 - 频繁创建/销毁 goroutine(比如每个 HTTP 请求都
go handle()且不节制)会导致调度器压力上升,但瓶颈通常不在“线程不够”,而在 channel 争用或 GC 压力
channel 缓冲区大小不是性能开关,而是语义开关
写 make(chan int, 100) 不是为了“加速”,而是为了表达“允许最多积压 100 个未处理任务”。缓冲区设错,轻则逻辑错乱,重则死锁。
- 无缓冲 channel(
make(chan int)):发送和接收必须同步发生,适合严格顺序协作(如主协程等 worker 回应) - 带缓冲 channel:发送不阻塞,直到缓冲满;适合解耦生产/消费节奏(如日志收集、批量上报)
- 缓冲区过大(如
make(chan *Request, 10000))可能掩盖背压问题,导致 OOM;建议配合select+default做非阻塞尝试,或用sem限流
并发控制别靠“手动 sleep”或“猜时间”
time.Sleep() 在并发协调中几乎总是错的。它既不可靠(GC STW、调度延迟会让实际休眠远超预期),又难维护(谁来调?调多久?)。
- 等待 goroutine 结束,请用
sync.WaitGroup或context.WithTimeout+ channel 关闭信号 - 限制并发数,用带缓冲的 channel 模拟信号量:
sem := make(chan struct{}, 5),每次执行前sem ,结束后 <code> - 取消长任务,必须传入
context.Context,并在 I/O 或循环中定期检查ctx.Done(),不能靠 sleep 中断
最常被忽略的一点:goroutine 泄漏往往不是因为没关 channel,而是闭包捕获了外部变量导致无法被 GC,或者 channel 发送端未关闭而接收端已退出——这类问题在线上压测时才暴露,但排查成本极高。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











