2
1 package main
2
3 import "time"
4
5 func main() {
6     m1 := make(map[string]int)
7     m1["hello"] = 1
8     m1["world"] = 2
9     go func() {
10         for i := 0; i < 100000000; i++ {
11             _ = m1["hello"]
12         }
13     }()
14     time.Sleep(100 * time.Millisecond)
15     m2 := make(map[string]int)
16     m2["hello"] = 3
17     m1 = m2
18 }

I run command go run --race with this code and get:

==================
WARNING: DATA RACE
Read at 0x00c420080000 by goroutine 5:
  runtime.mapaccess1_faststr()
      /usr/local/go/src/runtime/hashmap_fast.go:208 +0x0
  main.main.func1()
      /Users/meitu/test/go/map.go:11 +0x80

Previous write at 0x00c420080000 by main goroutine:
  runtime.mapassign()
      /usr/local/go/src/runtime/hashmap.go:485 +0x0
  main.main()
      /Users/meitu/test/go/map.go:16 +0x220

Goroutine 5 (running) created at:
  main.main()
      /Users/meitu/test/go/map.go:13 +0x1aa
==================

m1 and m2 are different variables,why do line 16 and line 11 cause data race?

My go version is 1.8. I guess that is some compile optimization, and somebody can tell me about it? Thank you very much.

icza
  • 389,944
  • 63
  • 907
  • 827
黄英俊
  • 21
  • 2
  • 1
    Seems somewhere the line numbers got mixed up. I see a race between 11 and 17. – Volker Sep 15 '17 at 07:14
  • @Volker Actually my go tool prints both as data race, #16 is a consequence of the first data race as detailed in my answer. – icza Sep 15 '17 at 07:38

2 Answers2

3

The requirements to have a data race are:

  1. Multiple goroutines accessing the same resource (e.g. a variable) concurrently.
  2. At least one of those accesses is a write.
  3. The accesses are uncynchronized.

In your code all 3 requirements are met:

  1. You have the main goroutine accesssing m1, and the one you start in it also accesses m1. The main goroutine accesses it after the other goroutine has been launched, so they are concurrent.
  2. The main goroutine writes m1 in line #17: m1 = m2.
  3. The accesses are not synchronized, you use no mutex or channels or anything like that (sleeping is not synchronization).

Therefore it's a data race.

The obvious data race is between lines #11 reading m1, and line #17 writing m1.

But! Since line #17 assigns m2 to m1, then when/if the launched goroutine continues to run, it attempts to read m1 which may now be the value of m2 because we assigned m2 to m1. What does this mean? This introduces another data race writing m2 and reading m1.

That is after line #17 if the program does not end immediately (it may, but not necessarily), then the launched goroutine attempts to read from m1 which is now m2 which was last written in line #16, so this explains the "conflict" between lines #11 and #16.

The full go run -race output is as follows:

==================
WARNING: DATA RACE
Write at 0x00c42000e010 by main goroutine:
  main.main()
      /home/icza/gows/src/play/play2.go:17 +0x22f

Previous read at 0x00c42000e010 by goroutine 5:
  [failed to restore the stack]

Goroutine 5 (running) created at:
  main.main()
      /home/icza/gows/src/play/play2.go:9 +0x190
==================
==================
WARNING: DATA RACE
Read at 0x00c42007e000 by goroutine 5:
  runtime.mapaccess1_faststr()
      /usr/local/go/src/runtime/hashmap_fast.go:208 +0x0
  main.main.func1()
      /home/icza/gows/src/play/play2.go:11 +0x7a

Previous write at 0x00c42007e000 by main goroutine:
  runtime.mapassign_faststr()
      /usr/local/go/src/runtime/hashmap_fast.go:598 +0x0
  main.main()
      /home/icza/gows/src/play/play2.go:16 +0x1fc

Goroutine 5 (running) created at:
  main.main()
      /home/icza/gows/src/play/play2.go:9 +0x190
==================
==================
WARNING: DATA RACE
Read at 0x00c420080088 by goroutine 5:
  main.main.func1()
      /home/icza/gows/src/play/play2.go:11 +0x90

Previous write at 0x00c420080088 by main goroutine:
  main.main()
      /home/icza/gows/src/play/play2.go:16 +0x212

Goroutine 5 (running) created at:
  main.main()
      /home/icza/gows/src/play/play2.go:9 +0x190
==================
Found 3 data race(s)
exit status 66
icza
  • 389,944
  • 63
  • 907
  • 827
  • yes, line #17 and line #11 is a data race, but I'm confused about why line #16 and line #11 is a data race , it is show in race detector print info. – 黄英俊 Sep 15 '17 at 07:21
  • @黄英俊 That's because if the launched goroutine continues to run after the assignment in the main goroutine in line #17, and reads `m1`, that may be `m2` as that was assigned, which was last written in line #16. See edited answer. – icza Sep 15 '17 at 07:36
  • Thanks. But I still have some confusion. Assign m2 to m1 is after writing m2, does this mean that this situation will never happen,it just a race detector warning ? – 黄英俊 Sep 15 '17 at 08:44
  • @黄英俊 The race detector only shows warnings for things that _did_ happen. You launch goroutine which keeps reading `m1`, then write `m2`, then assign`m2` to `m1`, so the launched goroutine will now read `m2` which was modified concurrently (after goroutine was launched), so reading `m2` through `m1` is also a data race. – icza Sep 15 '17 at 08:48
  • before assigning m2 to m1, the launched goroutine can't read m2 through m1. read m1, write m2 and assign m2 to m1 is executed in order. This is what I am confused – 黄英俊 Sep 15 '17 at 09:06
  • @黄英俊 The goroutine is launched which keeps reading `m1`. Then main goroutine creates `m2`, writes `m2`, then assigns `m2` to `m1`. All this happens concurrently with the launched goroutine. Now the launched goroutine will again read `m1` which is now `m2`, but `m2` was written concurrently, unsynchronized with the launched goroutine: data race. – icza Sep 15 '17 at 09:08
  • When m1 is m2, write m2 is ended. I don't understand "Now the launched goroutine will again read m1 which is now m2, but m2 was written concurrently" – 黄英俊 Sep 15 '17 at 09:12
  • @黄英俊 Maps are pointers under the hood. Writing `m2` map means writing the memory area pointed by it. When you assign `m2` to `m1`, the pointer is copied. Now reading `m1` map means reading the memory area pointed by it, which is the same memory area pointed by `m2`, which was modified concurrently with the launched goroutine. – icza Sep 15 '17 at 09:21
  • Thank you for your patience. I understand maps are pointer. may we have different understanding of the meaning for "concurrently"? "Now reading m1 map means reading the memory area pointed by it, which is the same memory area pointed by m2", after assigning m2 to m1, there is no goroutine is writing m2. – 黄英俊 Sep 15 '17 at 09:33
  • There are no sync mechanism that defines order or read and write on pointer `p` which is the adress of `m1["hello"] (m1["hello"] = m2["hello"] = *p)` – Sarath Sadasivan Pillai Sep 15 '17 at 10:33
1

Data race

A data race occurs when two goroutines access the same variable concurrently and at least one of the accesses is a write.

Instructions Reorder

compilers and processors may reorder the reads and writes executed within a single goroutine as far as the reordering does not change the behaviour within the routine, it doesn' ensure behaviour of other goroutines to be unaffected

m2["hello"] = 3
m1 = m2

Can be reordered to

m1 = m2
m2["hello"] = 3

That won't alter the behaviour of the main routine and thus race checking will consider that also for evaluating race condition. Now we have m2["hello"] = 3 causing the race condition and it prints out the same with its original line number

  • If this is the case (reordering causing the issue), then shouldn't disabling all optimizations and inlining by passing `-gcflags "-N -l"` to the go tool eliminate this data race? Yet it does not. – icza Sep 15 '17 at 07:48
  • 1
    reordering is not the cause as `m1=m2` anyways is a potential data race point . Reordering should cause it to evaluate assignment to `m2["hello"]=3` as equivalent to `m1["hello"] = 3` – Sarath Sadasivan Pillai Sep 15 '17 at 07:54
  • I understand that, but shouldn't the above mentioned flags disable reordering too? If they should, then the reordering will not happen and thus `m2["hello"] = 3` will not be equivalent to `m1["hello"] = 3`. – icza Sep 15 '17 at 08:05
  • Thanks. Can you tell more detail about it? If reordering cause this data race, how can I eliminate it? I need to do an experiment to help me understand the principles more deeply. – 黄英俊 Sep 15 '17 at 08:33
  • @icza instruction reorder is not the reason for any of the error , delting the answer. Was trying to us justify the race location – Sarath Sadasivan Pillai Sep 15 '17 at 09:52