gpt4 book ai didi

go - 在没有互斥体的情况下同时读取或写入时会发生什么

转载 作者:行者123 更新时间:2023-12-01 22:42:29 24 4
gpt4 key购买 nike

在 Go 中,一个 sync.Mutexchan用于防止共享对象的并发访问。但是,在某些情况下,我只对对象的变量或字段的“最新”值感兴趣。
或者我喜欢写一个值,而不关心另一个 go-routine 是否稍后覆盖它或刚刚覆盖它。
更新: TLDR;只是不要这样做。这是不安全的。阅读答案、评论和链接文档!
2021 年更新: Go 内存模型是 going to be specified more thoroughly还有three great articles由 Russ Cox 撰写,它将教您更多关于非同步内存访问的惊人影响。这些文章总结了下面的许多讨论和学习。
这里有两个变种 goodbad一个示例程序,其中两个似乎都使用当前的 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类型和附加到 slice 时,我总是需要互斥锁或类似的保护方法来避免段错误或意外行为。但是,对变量或字段值读取和写入文字(原子值)似乎是安全的。
问题:我可以安全地同时读取和安全地写入哪些 Go 数据类型,而无需使用 mutext,不会产生段错误,也不会从内存中读取垃圾?
解释为什么有些东西是 Go 中的安全或不安全 在你的回答中。
更新 :我重写了示例以更好地反射(reflect)原始代码,其中我遇到了并发写入问题。重要的倾向已经在评论中。我将接受一个足够详细地总结这些学习的答案(尤其是在 Go 运行时)。

最佳答案

However, in some cases I am just interested in the latest value of a variable or field of an object.



这是根本问题:“最新”这个词是什么意思?

假设,从数学上讲,我们有一系列值 Xi,其中 0 <= i < N。那么如果 j > i,Xj 显然“晚于”Xi。这是“最新”的一个很好的简单定义,可能是您想要的。

但是,当一台机器中的两个独立 CPU(包括 Go 程序中的两个 goroutine)同时工作时, 时间本身失去意义 .我们不能说是 i < j、i == j 还是 i > j。因此,latest 这个词没有正确的定义。

为了解决这类问题,现代 CPU 硬件和 Go 作为一种编程语言,为我们提供了某些同步原语。如果 CPU A 和 B 执行内存栅栏指令或同步指令,或使用任何其他硬件规定,CPU(和/或一些外部硬件)将插入“时间”概念所需的任何内容以恢复其含义。也就是说,如果 CPU 使用屏障指令,我们可以说在屏障之前执行的内存加载或存储是“之前”,而在屏障之后执行的内存加载或存储是“之后”。

(在一些现代硬件中,实际实现由加载和存储缓冲区组成,它们可以重新排列加载和存储进入内存的顺序。屏障指令要么同步缓冲区,要么在其中放置一个实际屏障,以便加载和存储商店不能越过障碍。这个特定的具体实现提供了一种简单的方法来考虑问题,但并不完整:您应该将时间视为根本不存在于硬件提供的同步之外,即所有负载来自和存储到,某些位置同时发生,而不是按顺序发生,除了这些障碍。)

无论如何,Go 的 sync package 为您提供了一种简单的高级访问方法来访问这些类型的障碍。在互斥体之前执行的编译代码 Lock call 确实在 lock 函数返回之前完成,并且 call 之后执行的代码直到 lock 函数返回后才真正开始。

Go 的 channel 提供相同类型的前后时间保证。

围棋 sync/atomic包提供了更低级别的保证。一般来说,您应该避免这种情况,转而使用更高级别的 channel 或 sync.Mutex。风格保证。 (编辑添加注释:您可以在这里使用 sync/atomicPointer 操作,但不能直接使用 string 类型,因为 Go 字符串实际上是作为包含两个单独值的 header 实现的:指针和长度. 你可以用另一个间接层来解决这个问题,通过更新指向 string 对象的指针。但在你考虑这样做之前,你应该对语言的首选方法的使用进行基准测试,并验证这些是否存在问题,因为在 sync/atomic 级别工作的代码很难编写和调试。)

关于go - 在没有互斥体的情况下同时读取或写入时会发生什么,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/61914041/

24 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com