
当go程序中使用select监听多个channel时,若主goroutine提前退出,其他goroutine会被强制终止,导致donechan等关闭信号无法被正常接收——这是并发同步缺失引发的典型问题。
在Go并发编程中,select语句本身是完全正确的:它会非阻塞地、随机公平地从所有已就绪的case中选择一个执行(若多个就绪,则伪随机选取;若均阻塞,则等待任一就绪)。你提供的select循环逻辑无语法或语义错误:
for {
fmt.Printf("Waiting for select statement ...\n")
select {
case req := <-requestChan:
fmt.Printf("I got a request: %v\n", req)
case <-doneChan:
fmt.Printf("serveDatabase: Got closing signal. Stop serving.\n")
return
}
}问题根源不在select,而在于程序生命周期管理缺失:当main函数执行完毕,整个进程立即终止,所有正在运行的goroutine(包括你的serveDatabase协程)会被强制杀死,无论其是否已完成或是否正阻塞在select中。
以你复现的场景为例:
// main goroutine 中:
requestChan <- Db_request{ request: "Login", beehive: "yaylaswiese" }
// requestChan <- ... // 注释掉第二条
fmt.Printf("Sending true to the doneChannel\n")
doneChan <- true // 发送成功,但 main 立即结束 → serveDatabase 协程被杀,来不及执行 case <-doneChan此时doneChan
立即学习“go语言免费学习笔记(深入)”;
✅ 正确做法:使用 sync.WaitGroup 显式等待协程退出:
package main
import (
"fmt"
"sync"
"time"
)
type Db_request struct {
request string
beehive string
}
func serveDatabase(requestChan <-chan Db_request, doneChan <-chan bool, wg *sync.WaitGroup) {
defer wg.Done() // 标记此goroutine完成
for {
fmt.Printf("Waiting for select statement ...\n")
select {
case req := <-requestChan:
fmt.Printf("I got a request: %v\n", req)
case <-doneChan:
fmt.Printf("serveDatabase: Got closing signal. Stop serving.\n")
return
}
}
}
func main() {
requestChan := make(chan Db_request, 10)
doneChan := make(chan bool, 1)
var wg sync.WaitGroup
wg.Add(1)
go serveDatabase(requestChan, doneChan, &wg)
// 模拟业务请求
requestChan <- Db_request{request: "Login", beehive: "yaylaswiese"}
// 发送关闭信号
fmt.Printf("Sending true to the doneChannel\n")
doneChan <- true
// 关键:等待协程安全退出,避免main提前结束
wg.Wait()
fmt.Println("All goroutines finished. Exiting.")
}⚠️ 注意事项:
- doneChan 建议使用 chan struct{} 替代 chan bool(零内存开销,语义更清晰:仅作通知,不传数据);
- 若需支持多次关闭或超时控制,可结合 context.WithCancel;
- 切勿依赖 time.Sleep() 等不确定延迟来“等待”协程——这是竞态条件的温床;
- 所有启动的goroutine都应有明确的退出机制与同步手段,这是Go并发健壮性的基石。
总结:Go中没有“后台线程”概念,所有goroutine均依附于主进程生命周期。显式同步不是可选项,而是并发安全的必选项。










