Accidentally two go routines received the same value from a channel?

I have had an occurence where two different go routines, read the same value from the same buffered channel, therefore creating a race condition.

Is this defined as possible somewhere ?

kind regards
klanghans

Normally, only one receiver gets a value out of a channel. Here are some things I can think of:

  • Are you closing the channel? When you close a channel, all receivers “receive” a zero value. You can use a 2-value channel receive: value, ok := <-channel and check ok to see if the value was actually received or if the channel was just closed.

  • Are you using the unsafe package to interact with channels anywhere to bypass the mutex that protects the channel?

  • Is it actually the same channel? Are you using some message queuing package whose API exposes a queue as a Go channel? If so, they might be implemented like this:

    (Your server code) -- go channel --> (their server) -- network --> (their client) -- go channel --> (Your client code)

$ cat racer.go
package main

import "time"

func main() {
	i := 42
	ch := make(chan *int, 2)
	go func() { i := <-ch; *i++ }()
	go func() { i := <-ch; *i++ }()
	ch <- &i
	ch <- &i
	time.Sleep(100 * time.Millisecond)
}

.

$ go run -race racer.go
==================
WARNING: DATA RACE
Read at 0x00c000016100 by goroutine 6:
  main.main.func1()
      /home/petrus/racer.go:8 +0x64

Previous write at 0x00c000016100 by goroutine 7:
  main.main.func2()
      /home/petrus/racer.go:9 +0x7a

Goroutine 6 (running) created at:
  main.main()
      /home/petrus/racer.go:8 +0x98

Goroutine 7 (finished) created at:
  main.main()
      /home/petrus/racer.go:9 +0xba
==================
Found 1 data race(s)
exit status 66
$ 

The Go Programming Language Specification

The Go Memory Model

Programs that modify data being simultaneously accessed by multiple goroutines must serialize such access.

To serialize access, protect the data with channel operations or other synchronization primitives such as those in the sync and sync/atomic packages.

If you must read the rest of this document to understand the behavior of your program, you are being too clever.

1 Like

Pointers! Good point! @klanghans If you send the same pointer into a channel twice, then two receiving goroutines can each get a copy of the pointer and you’ll introduce a race condition if both goroutines dereference it at the same time.

At any time without an intervening synchronization operation.

$ cat racer.go
package main

import "time"

func main() {
	i := 42
	ch := make(chan *int, 2)
	go func() { i := <-ch; *i++ }()
	ch <- &i
	time.Sleep(1 * time.Second)
	go func() { i := <-ch; *i++ }()
	ch <- &i
	time.Sleep(100 * time.Millisecond)
}

.

$ go run -race racer.go
==================
WARNING: DATA RACE
Read at 0x00c000016100 by goroutine 8:
  main.main.func2()
      /home/petrus/racer.go:11 +0x64

Previous write at 0x00c000016100 by goroutine 6:
  main.main.func1()
      /home/petrus/racer.go:8 +0x7a

Goroutine 8 (running) created at:
  main.main()
      /home/petrus/racer.go:11 +0xe9

Goroutine 6 (finished) created at:
  main.main()
      /home/petrus/racer.go:8 +0x98
==================
Found 1 data race(s)
exit status 66
$
1 Like

Thank you very much for your support, ideas and information.
Since i couldnt find anything in the specs, i wrote some tests to confirm:

  • One value sent over a channel can not be retrieved by multiple go routines (which is good)

It turned out, that if you are sending structs (pointers) over a channel and copy data into that struct you have to make sure, dereference the copy beforehand, otherwise a race condition occurs.

Lesson learned!

Thank you all for your support
kind regards

klanghans