在Go中,使用sync.Mutex
或chan
来防止并发访问共享对象。但是,在某些情况下,我只是对变量或对象字段的最新值感兴趣。
还是我喜欢写一个值,不在乎另一个go例程稍后会覆盖它还是之前已经覆盖它。
更新:TLDR;只是不要这样做。这不安全。阅读答案,评论和链接的文档!
这是示例程序的两个变体good
和bad
,其中两个变体似乎都使用当前的Go运行时产生“正确的”输出:
package main
import (
"flag"
"fmt"
"math/rand"
"time"
)
var bogus = flag.Bool("bogus",false,"use bogus code")
func pause() {
time.Sleep(time.Duration(rand.Uint32()%100) * time.Millisecond)
}
func bad() {
stop := time.After(100 * time.Millisecond)
var name string
// start some producers doing concurrent writes (DANGER!)
for i := 0; i < 10; i++ {
go func(i int) {
pause()
name = fmt.Sprintf("name = %d",i)
}(i)
}
// start consumer that shows the current value every 10ms
go func() {
tick := time.Tick(10 * time.Millisecond)
for {
select {
case <-stop:
return
case <-tick:
fmt.Println("read:",name)
}
}
}()
<-stop
}
func good() {
stop := time.After(100 * time.Millisecond)
names := make(chan string,10)
// start some producers concurrently writing to a channel (GOOD!)
for i := 0; i < 10; i++ {
go func(i int) {
pause()
names <- fmt.Sprintf("name = %d",i)
}(i)
}
// start consumer that shows the current value every 10ms
go func() {
tick := time.Tick(10 * time.Millisecond)
var name string
for {
select {
case name = <-names:
case <-stop:
return
case <-tick:
fmt.Println("read:",name)
}
}
}()
<-stop
}
func main() {
flag.Parse()
if *bogus {
bad()
} else {
good()
}
}
预期输出如下:
...
read: name = 3
read: name = 3
read: name = 5
read: name = 4
...
read:
和read: name=[0-9]
的任何组合都是此程序的正确输出。接收其他任何字符串作为输出将是错误的。
使用go run --race bogus.go
运行此程序是安全的。
但是,go run --race bogus.go -bogus
警告并发读写。
对于map
类型,当附加到切片时,我始终需要互斥或类似的保护方法,以避免出现段错误或意外行为。但是,将字面量(原子值)读写到变量或字段值似乎是安全的。
问题:我可以安全地读取和安全地并发哪些Go数据类型,而无需使用mutext,不会产生段错误以及无需从内存中读取垃圾?
请说明,为什么您的答案中在Go中安全或不安全。
更新:我重新编写了示例,以更好地反映原始代码,其中我遇到了并发写入问题。重要的观点已经在评论中。我将接受一个答案,该答案将足够详细地总结这些学习内容(尤其是在Go运行时中)。