Go语言多通道读写顺序:无缓冲通道结合信号量控制Goroutine并发问题
Let's walk through your code and break down the channel behavior, concurrency control logic, and potential pitfalls you should watch out for.
1. Semaphore Channel (sem) Read/Write Order
First, let's clarify: your sem is a buffered channel with capacity 10, not an unbuffered one. This is the standard way to implement a concurrency limiter in Go, and here's how its read/write order works:
- When a goroutine executes
sem <- struct{}{}:- If the channel has empty buffer slots (fewer than 10 values already sent), this write operation completes immediately—no blocking.
- Once the buffer is full (10 goroutines have acquired the semaphore), any additional goroutine trying to write to
semwill block. These blocked goroutines are queued in FIFO (first-in, first-out) order.
- When a goroutine exits, the
defer func() { <-sem }()runs: this reads one value from the channel, freeing up a buffer slot. The first goroutine that was blocked on writing tosemwill be woken up immediately to acquire the newly freed slot.
This FIFO behavior ensures that goroutines get access to the semaphore in the order they requested it, preventing starvation in most cases.
2. Concurrency Control Logic Breakdown
WaitGroup Usage
Your current WaitGroup setup only tracks a single goroutine (since you call wg.Add(1) once in main and only invoke DoSomething once). This works fine if DoSomething doesn't spawn additional goroutines, but it's a problem if you plan to recursively call DoSomething for the newSomethings you generate:
- If you loop through
newSomethingsand start newDoSomethinggoroutines, you must callwg.Add(1)before starting each new goroutine (otherwise,main'swg.Wait()will exit as soon as the firstDoSomethingfinishes, killing all in-progress goroutines).
For example, if you process newSomethings like this:
for _, url := range newSomethings { wg.Add(1) go DoSomething(url, sem, wg) }
This ensures every spawned goroutine is tracked by the WaitGroup.
Semaphore Safety
Your semaphore release logic is solid:
- The
defer func() { <-sem }()runs after the task completes (even if the function panics), so you'll never leak a semaphore slot. This is critical—leaking slots would gradually reduce your available concurrency until no more goroutines can run.
3. Key Notes & Potential Fixes
- Don't confuse buffered vs unbuffered channels: Your semaphore uses a buffered channel, which is correct for limiting concurrency to 10. An unbuffered channel would only allow 1 concurrent goroutine (since writes block until a read happens).
- WaitGroup scope: Make sure the same
WaitGroupinstance is passed to all spawnedDoSomethinggoroutines so they can all signal their completion. - Error handling: If your task (the
//......part) can return errors, consider adding error handling—right now, any panic would be recovered by the defer (since defer runs even on panic), but you might want to log or handle errors explicitly.
内容的提问来源于stack exchange,提问作者dm03514

