Go Worker Pool并发问题:sendOTP中phnno无法正常打印
1. 主程序提前退出,Worker未完成执行
当启动10000个goroutine时,主goroutine可能在所有Worker还没来得及执行fmt.Println(phnno)就直接终止了。Go程序的规则是主goroutine结束即终止整个程序,不会等待其他goroutine完成。而Worker数量较少时,主goroutine退出前,大部分Worker已经执行完输出逻辑,所以能正常看到结果。
你可以检查代码里是否用了sync.WaitGroup等待所有Worker完成,或是主goroutine在分发完任务后直接结束,没有做阻塞等待。
2. 标准输出缓冲未及时刷新
Go的os.Stdout是带缓冲的,当大量goroutine同时输出时,缓冲内容可能还没来得及刷到控制台,程序就已经退出了。少量Worker场景下,要么缓冲很快被填满自动刷新,要么程序退出前刚好触发了刷新,所以输出能正常显示。
解决方式:在sendOTP的输出语句后手动调用fmt.Flush(),或是改用os.Stderr输出——标准错误流是无缓冲的,内容会直接打印到控制台。
3. 任务队列或Worker逻辑存在问题
如果任务channel容量太小,主goroutine往队列塞任务时可能阻塞,若未处理好阻塞逻辑,部分任务可能无法被Worker接收,自然不会输出。另外,若主goroutine未正确关闭任务channel,Worker可能一直阻塞在读取操作上,没机会执行输出。
你可以检查任务channel的容量是否足够容纳所有任务,或是主goroutine在发完任务后是否及时关闭channel,让Worker知道没有新任务需要处理。
4. 调度压力导致部分Worker未被执行
虽然Go的goroutine轻量,但10000个同时运行时,调度器压力会增大,可能部分goroutine还没轮到被调度执行,程序就已经退出了。不过这个情况相对少见,核心问题还是主goroutine提前退出的概率更高。
- 用WaitGroup等待所有Worker完成:启动Worker前调用
wg.Add(workerCount),每个Worker执行完后调用wg.Done(),主goroutine最后调用wg.Wait(),确保所有Worker执行完毕再退出。 - 强制刷新输出缓冲:在
sendOTP输出phnno后添加fmt.Flush();或是直接用os.Stderr.WriteString(phnno + "\n")输出,跳过缓冲环节。 - 正确管理任务channel:确保任务channel容量足够容纳所有任务,或主goroutine发完任务后及时关闭channel,让Worker能正常退出循环。
举个简单的修复示例代码:
package main import ( "fmt" "sync" ) func sendOTP(phnno string, wg *sync.WaitGroup) { defer wg.Done() fmt.Println(phnno) // 可选:手动刷新缓冲 // fmt.Flush() } func main() { workerCount := 10000 tasks := make(chan string, workerCount) // 容量与任务数一致,避免阻塞 var wg sync.WaitGroup // 启动Worker池 for i := 0; i < workerCount; i++ { wg.Add(1) go func() { // 循环读取任务直到channel关闭 for phnno := range tasks { sendOTP(phnno, &wg) } }() } // 生成并发送任务 for i := 0; i < workerCount; i++ { tasks <- fmt.Sprintf("phone-%d", i) } close(tasks) // 关闭任务channel,通知Worker无新任务 wg.Wait() // 等待所有Worker完成任务 }
内容的提问来源于stack exchange,提问作者Adarsh Shahi

