Is it possible to have race condition with golang chan?

huangapple go评论82阅读模式
英文:

Is it possible to have race condition with golang chan?

问题

我的目标是创建一个简单的 WebSocket 服务器。我使用 chan 来分发消息,例如通过调用 <-messageChanmessageChan 有很多写入者和读取者。

然而,这个 StackOverflow 问题 让我担心会导致意外的死锁。

我所做的事情:

  1. 创建一个测试,基本上做以下操作:
    1. 用 0 到 1000 填充一个 chan int
    2. 创建 100 个 goroutine 来调用 <-chan 并将其添加到一个 map[int]bool 中。
    3. 如果 len(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))
	}
}

编辑:

  1. 代码在 map[int]bool 字段中查找重复项。编辑:这是因为 defer close(c)。我应该在每个操作中检查通道是否打开。

无论如何,这就是我学到的东西。希望对新的 Golangers 有所帮助。

所学到的教训:

  1. 对于一个通道来说,有很多写入者和读取者是可以的。
  2. 总是检查 val, isOpen := <-chan。如果 isOpen == false,则停止使用该通道。
  3. 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 &lt;-messageChan. the messageChan have many writers and readers.

However, this StackOverflow question scares me of causing an unintentionally deadlock.

What I've did:

  1. Create a test that essentially do:
    1. populate a chan int with 0 to 1000.
    2. create 100 goroutine to invoke &lt;-chan and add it to a map[int]bool.
    3. invoke t.Fatal if len(map[int]bool) is not 1000. In other words, race condition.

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 (
	&quot;log&quot;
	&quot;sync&quot;
	&quot;testing&quot;
)

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(&quot;race condition&quot;)
	}
	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 &lt; l; i++ {
		c &lt;- i
	}

	mux := MapMux{sync.RWMutex{}, map[int]bool{}, sync.WaitGroup{}}

	mux.Add(l)

	for i := 0; i &lt; 100; i++ {
		go func(key int) {
			for {
				payload := &lt;-c
				log.Printf(&quot;go%d: %d&quot;, key, payload)
				mux.AddInt(payload)
			}
		}(i)
	}

	mux.Wait()

	if len(mux.m) != l {
		t.Fatal(&quot;expected len:&quot;, l, &quot;, actual len:&quot;, len(mux.m))
	}
}

Edit:

  1. The code finds duplicates in the map[int]bool field. Edit: This is because defer 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:

  1. it is okay to have many writers and many readers for one channel.
  2. always check val, isOpen := &lt;-chan. If isOpen == false, then stop using the channel.
  3. 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(&quot;race condition&quot;)
         }
         mux.RUnlock()
         mux.Lock()
         if _, isExist := mux.m[i]; isExist {
             log.Fatal(&quot;race condition&quot;)
         }
         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(&quot;race condition&quot;)
    }
    mux.RUnlock()
    mux.Lock()
    if _, isExist := mux.m[i]; isExist {
        log.Fatal(&quot;race condition&quot;)
    }
    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.

huangapple
  • 本文由 发表于 2022年9月30日 11:03:18
  • 转载请务必保留本文链接:https://go.coder-hub.com/73903603.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定