最常用最推荐的方式是用带缓冲通道模拟信号量:sem := make(chan struct{}, maxWorkers),容量即最大并发数,通过发送/接收空结构体实现令牌获取与释放,天然线程安全。

Go语言中控制并发数量最常用、最推荐的方式是使用 带缓冲的通道(channel) 模拟信号量(Semaphore),它轻量、安全、符合Go的并发哲学,无需引入额外包。
用带缓冲channel实现信号量
本质是把 channel 当作“资源池”:容量即最大并发数,每次协程执行前尝试从 channel 取一个“令牌”(发送任意值),执行完再归还(接收或关闭后清空)。由于 channel 的发送/接收天然阻塞且 goroutine 安全,无需加锁。
- 初始化:
sem := make(chan struct{}, maxWorkers),例如maxWorkers = 3表示最多 3 个任务并发 - 获取许可:
sem —— 若已满,则阻塞,直到有协程释放 - 释放许可:
或在 defer 中执行,确保即使 panic 也归还
封装成可复用的 Semaphore 类型
为提升可读性和复用性,可简单封装:
type Semaphore struct {
ch chan struct{}
}
<p>func NewSemaphore(n int) *Semaphore {
return &Semaphore{ch: make(chan struct{}, n)}
}</p><p>func (s <em>Semaphore) Acquire() { s.ch Semaphore) Release() { </em></p>
使用时:sem.Acquire(); defer sem.Release(),语义清晰,不易遗漏释放。
与 sync.WaitGroup + 互斥锁方案对比
有人用 sync.Mutex 配合计数器模拟信号量,但存在明显缺点:
- 需手动管理临界区,易因忘记 Unlock 或 panic 导致死锁
- 无法天然阻塞等待,要配合
sync.Cond或轮询,代码复杂且低效 - 不满足 Go “通过通信共享内存” 的设计思想
而 channel 方案一行阻塞、自动调度、无竞态风险,是更地道的选择。
实际应用示例:并发请求限流
比如批量调用 HTTP 接口,限制最多 5 路并发:
sem := NewSemaphore(5)
var wg sync.WaitGroup
<p>for _, url := range urls {
wg.Add(1)
go func(u string) {
defer wg.Done()
sem.Acquire()
defer sem.Release()</p><pre class="brush:php;toolbar:false;"> resp, err := http.Get(u)
// 处理 resp / err
}(url)} wg.Wait()
这样既控制了资源占用,又避免了服务端被压垮,逻辑清晰可控。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











