英文:
Is it possible to have race condition with golang chan?
问题
我的目标是创建一个简单的 WebSocket 服务器。我使用 chan 来分发消息,例如通过调用 <-messageChan
。messageChan
有很多写入者和读取者。
然而,这个 StackOverflow 问题 让我担心会导致意外的死锁。
我所做的事情:
- 创建一个测试,基本上做以下操作:
- 用 0 到 1000 填充一个
chan int
。 - 创建 100 个 goroutine 来调用
<-chan
并将其添加到一个map[int]bool
中。 - 如果
len(map[int]bool)
不等于 1000,则调用t.Fatal
。换句话说,这是竞争条件。
- 用 0 到 1000 填充一个
然而,测试没有失败。我担心我做错了什么,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)
。我应该在每个操作中检查通道是否打开。
无论如何,这就是我学到的东西。希望对新的 Golangers 有所帮助。
所学到的教训:
- 对于一个通道来说,有很多写入者和读取者是可以的。
- 总是检查
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() }
英文:
My goal is to create a simple websocket server. I use chan to distribute the message e.g by invoking <-messageChan
. the messageChan
have many writers and readers.
However, this StackOverflow question scares me of causing an unintentionally deadlock.
What I've did:
- Create a test that essentially do:
- populate a
chan int
with 0 to 1000. - create 100 goroutine to invoke
<-chan
and add it to amap[int]bool
. - invoke
t.Fatal
iflen(map[int]bool)
is not 1000. In other words, race condition.
- populate a
However, the test did not fail. I am afraid, I did something wrong, and chan
can have deadlock.
The code
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))
}
}
Edit:
- The code finds duplicates in the
map[int]bool
field. Edit: This is becausedefer close(c)
. I should have check is the channel was open for every operation.
Anyway, this is what I learn. Hope this help new Golangers.
Lesson learnt:
- it is okay to have many writers and many readers for one channel.
- always check
val, isOpen := <-chan
. IfisOpen
==false
, then stop using the channel. sync.RWMutex.RLock()
does not guarantee other goroutine from making changes to the map. So, becareful. This is how it should be done.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() }
答案1
得分: 0
如果在goroutine之间共享内存,就会发生数据竞争。如果不在goroutine之间共享内存,那么你担心的那种数据竞争就不会发生。还可能存在其他竞争条件,比如你在AddInt
方法中的那个。正确的版本应该是:
func (mux *MapMux) AddInt(i int) {
mux.RLock()
if _, isExist := mux.m[i]; isExist {
log.Fatal("竞争条件")
}
mux.RUnlock()
mux.Lock()
if _, isExist := mux.m[i]; isExist {
log.Fatal("竞争条件")
}
mux.m[i] = true
mux.Unlock()
mux.Done()
}
这是因为不能保证在RUnlock和Lock之间没有其他的goroutine修改了map。
在你的示例中,你在goroutine之间共享了一个map。这意味着你必须使用互斥锁来保护对它的所有访问。如果你这样做,那么在使用该map时就不会有数据竞争。
然而,一般来说,如果能够避免共享内存,你的程序就不会出现这种数据竞争。
英文:
A data race happens if you share memory between goroutines. If you do not share memory between goroutines, the kind of data races you are afraid of will not happen. There can be other race conditions, such as the one you have in your AddInt
method. The correct version should be:
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()
}
This is because there is no guarantee that another goroutine changes the map between RUnlock and Lock.
In your example, you are sharing a map between goroutines. That means you have to protect all access to it using a mutex. If you do that, there will be no data races around the use of that map.
However, in general, if you can avoid sharing memory, your program will not have such data races.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论