
本文详解 Go 中因通道阻塞导致数据无法接收的典型并发问题,重点说明 chan interface{} 的正确使用方式、goroutine 启动顺序、通道关闭机制,并提供可运行的修复代码与最佳实践。
本文详解 go 中因通道阻塞导致数据无法接收的典型并发问题,重点说明 `chan interface{}` 的正确使用方式、goroutine 启动顺序、通道关闭机制,并提供可运行的修复代码与最佳实践。
在 Go 中使用通道(channel)协调数据库查询的并发执行时,一个常见却隐蔽的错误是同步调用阻塞了通道发送。你的代码中,getdataList 函数先调用 RunQuery(ch, ...)(同步执行),而 RunQuery 内部执行 ch <- tableData 时,由于此时没有 goroutine 在另一端接收该值,该操作将永久阻塞——导致 RunQuery 无法返回,进而使后续启动接收 goroutine 的 go func() { ... }() 永远不会被执行。这就是你“收不到数据”的根本原因。
✅ 正确做法:发送与接收必须并发进行
必须确保 ch <- tableData 和 <-ch 位于独立且同时运行的 goroutine 中。因此,RunQuery 本身也应作为 goroutine 启动:
func getdataList(id int) {
ch := make(chan interface{})
done := make(chan bool)
// ✅ 关键修复1:RunQuery 必须异步执行
go RunQuery(ch, "SELECT id, name, last_name, first_name FROM persons WHERE id = ?", id)
// ✅ 关键修复2:启动接收 goroutine(保持原逻辑)
go func() {
for {
x, ok := <-ch
if ok {
fmt.Printf("Received data for ID %d: %+v\n", id, x)
} else {
fmt.Printf("Channel closed for ID %d\n", id)
done <- true
return
}
}
}()
// ✅ 关键修复3:等待完成(避免函数立即返回导致 goroutine 被回收)
<-done
}⚠️ 补充关键修复:务必关闭通道,防止死锁
未关闭的无缓冲通道在所有发送完成后仍保持打开状态,接收端 for range ch 或 x, ok := <-ch 中的 ok 将永远为 true,造成逻辑卡死或资源泄漏。应在 RunQuery 发送完毕后显式关闭通道:
func RunQuery(ch chan interface{}, query string, param interface{}) {
defer func() {
if r := recover(); r != nil {
fmt.Println("RunQuery panicked:", r)
close(ch) // 即使出错也要关闭,避免接收方永久阻塞
return
}
}()
stmt, err := db.Prepare(query)
if err != nil {
fmt.Printf("Prepare error: %v\n", err)
close(ch)
return
}
defer stmt.Close()
rows, err := stmt.Query(param)
if err != nil {
fmt.Printf("Query error: %v\n", err)
close(ch)
return
}
defer rows.Close()
columns, err := rows.Columns()
if err != nil {
fmt.Printf("Columns error: %v\n", err)
close(ch)
return
}
count := len(columns)
tableData := make([]map[string]interface{}, 0)
values := make([]interface{}, count)
valuePtrs := make([]interface{}, count)
for rows.Next() {
for i := range valuePtrs {
valuePtrs[i] = &values[i]
}
if err := rows.Scan(valuePtrs...); err != nil {
fmt.Printf("Scan error: %v\n", err)
continue
}
entry := make(map[string]interface{})
for i, col := range columns {
val := values[i]
if b, ok := val.([]byte); ok {
entry[col] = string(b)
} else {
entry[col] = val
}
}
tableData = append(tableData, entry)
}
// ✅ 发送结果后立即关闭通道
ch <- tableData
close(ch) // ← 至关重要!
}? 其他重要注意事项
- fmt.Pritln 是拼写错误:应为 fmt.Println,否则编译失败;
- defer db.Close() 放在 main 中无效:db 是全局变量,defer 只对当前函数生效;应移至 main 结尾或使用 defer db.Close() 在 main 中显式调用;
- HTTP handler 中的 goroutine 缺乏同步控制:dbtest 启动两个 go getdataList(...) 后立即返回,可能导致响应提前结束。生产环境建议使用 sync.WaitGroup 或 context 控制生命周期;
- SQL 注入风险:当前使用 ? 占位符是安全的,但切勿字符串拼接 SQL;
- 错误处理需统一:示例中已增强 RunQuery 的 panic 捕获与错误路径关闭通道逻辑,提升健壮性。
✅ 最终建议结构(简洁可靠版)
func getdataList(id int) {
ch := make(chan interface{})
go RunQuery(ch, "SELECT ... WHERE id = ?", id)
// 使用 range 自动处理关闭
for data := range ch {
fmt.Printf("ID=%d → %+v\n", id, data)
}
}通过以上修正,通道收发双方真正并发运行,数据可被及时接收,程序逻辑清晰、无死锁风险,符合 Go 并发编程的核心原则:Don’t communicate by sharing memory; share memory by communicating.


















