现在当我们运行测试时获得的结果(如果没有得到 —— 参考上面的做法):
-------- FAIL: TestCheckWebsites (0.00s)
CheckWebsites_test.go:31: Wanted map[http://google.com:true http://blog.gypsydave5.com:true waat://furhurterwe.geds:false], got map[waat://furhurterwe.geds:false]
FAIL
exit status 1
FAIL github.com/gypsydave5/learn-go-with-tests/concurrency/v1 0.010s
urlforurlsurlurl
解决这个问题:
uurluurlgoroutineuurl
现在,如果你幸运的话,你会得到:
PASS
ok github.com/gypsydave5/learn-go-with-tests/concurrency/v1 2.012s
但是,如果你不走运(如果你运行基准测试,这很可能会发生,因为你将发起多次的尝试)。
fatal error: concurrent map writes
goroutine 8 [running]:
runtime.throw(0x12c5895, 0x15)
/usr/local/Cellar/go/1.9.3/libexec/src/runtime/panic.go:605 +0x95 fp=0xc420037700 sp=0xc4200376e0 pc=0x102d395
runtime.mapassign_faststr(0x1271d80, 0xc42007acf0, 0x12c6634, 0x17, 0x0)
/usr/local/Cellar/go/1.9.3/libexec/src/runtime/hashmap_fast.go:783 +0x4f5 fp=0xc420037780 sp=0xc420037700 pc=0x100eb65
github.com/gypsydave5/learn-go-with-tests/concurrency/v3.WebsiteChecker.func1(0xc42007acf0, 0x12d3938, 0x12c6634, 0x17)
/Users/gypsydave5/go/src/github.com/gypsydave5/learn-go-with-tests/concurrency/v3/websiteChecker.go:12 +0x71 fp=0xc4200377c0 sp=0xc420037780 pc=0x12308f1
runtime.goexit()
/usr/local/Cellar/go/1.9.3/libexec/src/runtime/asm_amd64.s:2337 +0x1 fp=0xc4200377c8 sp=0xc4200377c0 pc=0x105cf01
created by github.com/gypsydave5/learn-go-with-tests/concurrency/v3.WebsiteChecker
/Users/gypsydave5/go/src/github.com/gypsydave5/learn-go-with-tests/concurrency/v3/websiteChecker.go:11 +0xa1
... many more scary lines of text ...
fatal error: concurrent map writesresultsfatal error
这是一种 race condition(竞争条件),当软件的输出取决于事件发生的时间和顺序时,因为我们无法控制,bug 就会出现。因为我们无法准确控制每个 goroutine 写入结果 map 的时间,两个 goroutines 同一时间写入时程序将非常脆弱。
racego test -race
你应该得到一些如下所示的输出:
==================
WARNING: DATA RACE
Write at 0x00c420084d20 by goroutine 8:
runtime.mapassign_faststr()
/usr/local/Cellar/go/1.9.3/libexec/src/runtime/hashmap_fast.go:774 +0x0
github.com/gypsydave5/learn-go-with-tests/concurrency/v3.WebsiteChecker.func1()
/Users/gypsydave5/go/src/github.com/gypsydave5/learn-go-with-tests/concurrency/v3/websiteChecker.go:12 +0x82
Previous write at 0x00c420084d20 by goroutine 7:
runtime.mapassign_faststr()
/usr/local/Cellar/go/1.9.3/libexec/src/runtime/hashmap_fast.go:774 +0x0
github.com/gypsydave5/learn-go-with-tests/concurrency/v3.WebsiteChecker.func1()
/Users/gypsydave5/go/src/github.com/gypsydave5/learn-go-with-tests/concurrency/v3/websiteChecker.go:12 +0x82
Goroutine 8 (running) created at:
github.com/gypsydave5/learn-go-with-tests/concurrency/v3.WebsiteChecker()
/Users/gypsydave5/go/src/github.com/gypsydave5/learn-go-with-tests/concurrency/v3/websiteChecker.go:11 +0xc4
github.com/gypsydave5/learn-go-with-tests/concurrency/v3.TestWebsiteChecker()
/Users/gypsydave5/go/src/github.com/gypsydave5/learn-go-with-tests/concurrency/v3/websiteChecker_test.go:27 +0xad
testing.tRunner()
/usr/local/Cellar/go/1.9.3/libexec/src/testing/testing.go:746 +0x16c
Goroutine 7 (finished) created at:
github.com/gypsydave5/learn-go-with-tests/concurrency/v3.WebsiteChecker()
/Users/gypsydave5/go/src/github.com/gypsydave5/learn-go-with-tests/concurrency/v3/websiteChecker.go:11 +0xc4
github.com/gypsydave5/learn-go-with-tests/concurrency/v3.TestWebsiteChecker()
/Users/gypsydave5/go/src/github.com/gypsydave5/learn-go-with-tests/concurrency/v3/websiteChecker_test.go:27 +0xad
testing.tRunner()
/usr/local/Cellar/go/1.9.3/libexec/src/testing/testing.go:746 +0x16c
==================
WARNING: DATA RACE
Write at 0x00c420084d20 by goroutine 8:.
正在写入相同的内存块
Previous write at 0x00c420084d20 by goroutine 7:
最重要的是,我们可以看到发生写入的代码行:
/Users/gypsydave5/go/src/github.com/gypsydave5/learn-go-with-tests/concurrency/v3/websiteChecker.go:12
和 goroutines 7 和 8 开始的代码行号:
/Users/gypsydave5/go/src/github.com/gypsydave5/learn-go-with-tests/concurrency/v3/websiteChecker.go:11
你需要知道的所有内容都会打印到你的终端上 - 你只需耐心阅读就可以了。
Channels
我们可以通过使用 channels 协调我们的 goroutines 来解决这个数据竞争。channels 是一个 Go 数据结构,可以同时接收和发送值。这些操作以及细节允许不同进程之间的通信。
WebsiteChecker
resultsresultChannelmakechan resultresultresultWebsiteCheckerstringbool
mapwcresultresultChannel<-
// send statement
resultChannel <- result{u, wc(u)}
for<-
// receive expression
result := <-resultChannel
result