Golang chan是否存在竞态条件?多读写场景下的死锁疑虑
关于Go中多读写goroutine下Chan的死锁与竞态问题
我想搭建一个简单的WebSocket服务器,用chan来做消息分发,比如调用<-messageChan,这个messageChan会有多个写入者和读取者。但参考相关讨论后,我担心这种场景下会意外触发死锁。
为此我写了个测试来验证:
- 向
chan int中填充0到1000的数值; - 创建100个goroutine,每个goroutine不断从chan中取值并存入
map[int]bool; - 最后检查
map[int]bool的长度是否为1000,以此判断是否存在竞态条件,若不符合则调用t.Fatal。
但测试一直没有失败,我怀疑自己的测试逻辑有问题,同时也不确定chan在多读写场景下到底会不会引发死锁。
以下是测试代码(main_test.go):
package main import ( "log" "sync" "testing" ) type MapMux struct { sync.RWMutex m map[int]bool sync.WaitGroup } func (mux *MapMux) AddInt(i int) { mux.RLock() if _, isExist := mux.m[i]; isExist { log.Fatal("race condition") } mux.RUnlock() mux.Lock() mux.m[i] = true mux.Unlock() mux.Done() } func TestChanRaceCondition(t *testing.T) { l := 1000 c := make(chan int, l) defer close(c) for i := 0; i < l; i++ { c <- i } mux := MapMux{sync.RWMutex{}, map[int]bool{}, sync.WaitGroup{}} mux.Add(l) for i := 0; i < 100; i++ { go func(key int) { for { payload := <-c log.Printf("go%d: %d", key, payload) mux.AddInt(payload) } }(i) } mux.Wait() if len(mux.m) != l { t.Fatal("expected len:", l, ", actual len:", len(mux.m)) } }
编辑补充
后续发现代码会检测到map[int]bool存在重复值,问题出在使用了defer close(c),正确的做法是每次从chan取值时都要检查通道是否处于开启状态。
最终总结的经验
- 单个chan原生支持多个写入者和读取者;
- 必须始终使用
val, isOpen := <-chan的方式检查通道状态,若isOpen为false则要停止对该通道的操作; sync.RWMutex.RLock()只能保证读取的一致性,无法阻止其他goroutine修改map,要注意锁的正确使用逻辑,修正后的示例代码如下:
func (mux *MapMux) AddInt(i int) { mux.RLock() if _, isExist := mux.m[i]; isExist { log.Fatal("race condition") } mux.RUnlock() mux.Lock() if _, isExist := mux.m[i]; isExist { log.Fatal("race condition") } mux.m[i] = true mux.Unlock() mux.Done() }
内容的提问来源于stack exchange,提问作者Jason Rich Darmawan
相关产品推荐
相关产品推荐

