
Go语言并发编程:锁与通道关闭的陷阱
Go语言中,channel和mutex是处理并发问题的利器,但两者结合使用时,容易出现意想不到的错误,例如本文要讨论的“panic: send on closed channel”问题。即使使用了mutex锁,仍然可能出现此错误。
问题重现
以下代码片段演示了这个问题:
package main
import (
"context"
"fmt"
"sync"
)
var lock sync.Mutex
func main() {
c := make(chan int, 10)
wg := sync.WaitGroup{}
ctx, cancel := context.WithCancel(context.TODO())
wg.Add(1)
go func() {
defer wg.Done()
lock.Lock()
cancel()
close(c)
lock.Unlock()
}()
for i := 0; i
<h3>问题根源分析</h3>
<p>代码中,<code>lock.Lock()</code> 和 <code>lock.Unlock()</code> 保证了<code>close(c)</code>操作的原子性,防止多个goroutine同时关闭通道。然而,<code>select</code>语句的非确定性导致问题。即使通道<code>c</code>已关闭,<code>case c 仍然可能被选中,从而引发panic。这是因为<code>select</code>语句在通道关闭后,会继续尝试发送数据,直到发现通道已关闭。</code></p><div class="aritcle_card flexRow artxards">
<div class="artcardd flexRow">
<a class="aritcle_card_img" rel="nofollow" href="/xiazai/gongju/2500" title="度加AI"><img
src="https://img.php.cn/upload/manual/000/969/633/6a61700fe297c535.jpg" alt="度加AI" onerror="this.onerror='';this.src='/static/lhimages/moren/morentu.png'" ></a>
<div class="aritcle_card_info flexColumn">
<a rel="nofollow" href="/xiazai/gongju/2500" title="度加AI" class="overflowclass">度加AI</a>
<p class="overflowclass">度加AI官网入口,百度官方 AIGC 创作平台,支持 AI 成片、AI 生文、数字人、声音克隆、配音字幕与智能剪辑等在线创作能力。</p>
</div>
<a rel="nofollow" href="/xiazai/gongju/2500" title="度加AI" class="aritcle_card_btn flexRow flexcenter"><b></b><span>下载</span>
</a>
</div>
</div>
<h3>解决方案</h3>
<p>为了避免panic,需要在发送数据前检查通道是否关闭,或者使用上下文机制优雅地关闭goroutine。以下改进后的代码使用上下文机制:</p>
<pre class="brush:php;toolbar:false;">package main
import (
"context"
"fmt"
"sync"
)
func main() {
c := make(chan int, 10)
wg := sync.WaitGroup{}
ctx, cancel := context.WithCancel(context.TODO())
wg.Add(1)
go func() {
defer wg.Done()
cancel() // 先取消上下文
close(c)
}()
for i := 0; i
<p>此版本中,我们先取消上下文,再关闭通道。<code>select</code>语句中的<code>case 会优先处理上下文取消信号,避免向已关闭的通道发送数据。 这是一种更健壮的处理并发问题的方案。 直接检查通道是否关闭也是一种可行的方案,但上下文机制通常更优雅且易于维护。</code></p>golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!










