Go 在没有锁的情况下并发读取函数指针安全吗?

Go 在没有锁的情况下并发读取函数指针安全吗?,go,concurrency,goroutine,Go,Concurrency,Goroutine,假设我有这个: go func() { for range time.Tick(1 * time.Millisecond) { a, b = b, a } }() 以及其他地方: i := a // <-- Is this safe? i:=a/就……而言,它不安全。简而言之,我对竞争条件的理解是,当有多个异步例程(协同例程、线程、进程、goroutine等)试图访问同一资源,并且至少有一个是写入操作时,因此在您的示例中,我们有两个goroutine读取

假设我有这个:

go func() {
    for range time.Tick(1 * time.Millisecond) {
        a, b = b, a
    }
}()
以及其他地方:

i := a // <-- Is this safe?
i:=a/就……而言,它不安全。简而言之,我对竞争条件的理解是,当有多个异步例程(协同例程、线程、进程、goroutine等)试图访问同一资源,并且至少有一个是写入操作时,因此在您的示例中,我们有两个goroutine读取和写入function类型的变量,我认为,从并发的角度来看,重要的是这些变量在某个地方有一个内存空间,我们试图读取或写入该部分内存

简短回答:只需使用带有
go run-race

或者
go build-race
,您将看到检测到的数据竞争

对来自多个goroutine的任何变量的非同步并发访问,其中至少有一个是write is未定义的行为

未定义的意思是:未定义。可能是您的程序工作正常,也可能是工作不正确。它可能会导致丢失Go运行时提供的内存和类型安全性(请参见下面的示例)。它甚至可能使你的程序崩溃。或者它甚至可能导致地球爆炸(爆炸的概率非常小,甚至可能小于1e-40,但仍然…)

在您的案例中,此未定义表示是,
i
可能是
nil
,部分分配,无效,未定义。。。除了
a
b
之外的任何内容。这个列表只是所有可能结果的一小部分

不要认为某些数据竞争是(或可能是)良性或无害的。如果无人看管,它们可能是最糟糕事情的根源

由于您的代码在一个goroutine中写入变量
a
,并在另一个goroutine中读取该变量(该goroutine尝试将其值分配给另一个变量
i
),因此这是一个数据争用,因此不安全。在您的测试中,它是否“正确”工作并不重要。有人可能会以您的代码为起点,对其进行扩展/构建,并由于您最初的“无害”数据竞争而导致灾难

有关问题,请阅读并回答

强烈建议阅读Dmitry Vyukov的博文:


还有一篇非常有趣的博文,其中展示了一个通过有意的数据竞争打破Go内存安全的例子:

我想是的。看看这个:()指针的大小总是4个字节。在32位处理器中,字大小为32位(4字节)。显然,在64位处理器中,有8个字节的字。因此,基于这一点以及您在文档中发布的代码片段,我想说它是安全的。@AmirKeibi需要注意的重要一点是,文档中的保证并不是说单个机器字操作是原子的。它只是说大于一个机器字的操作是无序的。实际上,无论读写操作是否是一个单机器字操作,都无法保证(也无法保证)该操作是原子操作。它依赖于硬件,从Go的角度来看没有定义,因此需要同步。我认为这个问题的标题有点误导。并发读取是安全的,但当至少涉及一个写入操作时,所有内容都会崩溃。这是一个好的观点。但我不认为他的问题是关于比赛条件的。“也就是说,我是否可能为零,部分赋值,无效,未定义,……a或b以外的任何东西?”唯一的问题是,赋值给我是否安全。实际上,他澄清了问题是什么:“a或b以外的任何东西?”@AmirKeibi,因为存在种族条件,答案是肯定的。我的问题不是关于数据竞赛。虽然
-race
确实显示了一个race,但如果该值始终保证有效,我并不关心它。我相信这最终更多的是一个硬件问题,以及当并发读取发生时,写入地址的值是否可能处于无效状态。我还建议阅读OP,以便更好地掌握潜在的H/W问题。总而言之,在OP中,当他们在文本编辑器中编写代码时,代码“以下”存在两个问题:1)编译器可以生成机器代码,该代码对变量的内存位置执行“奇怪”操作;2) 多CPU和/或核心硬件存在缓存一致性问题:当一个CPU从内存中读取一个值时,它不一定是从另一个CPU写入的同一个位置读取该值。哦,第三个问题是:3)广泛使用的H/W平台上的当代CPU经常执行内存访问的重新排序。当程序员除了语言的内存模型所保证的之外还有期望时,所有这三个问题只会“破坏东西”,所以请不要有它们@这是我的观点。你不能保证
i
的值是有效的,这就是未定义的意思。你的程序甚至可能崩溃,我想你不认为这是良性的。在当前的编译器、硬件和生成的代码中,您可能不会遇到这种情况,但您没有保证。甚至可能是下一个版本的GO编译器将生成一个不同的“优化”代码,它会根据您希望程序执行的“错误行为”——仅仅因为您在代码中留下了数据竞争。@ NICROBOBOT,考虑使用<代码>同步/原子< /代码>操作来访问您的值:您将无法得到任何订购保证。(你不需要它们)但是,在内存模型方面,您是安全的,因为这些功能可以确保在需要的地方设置适当的内存围栏。您可以在
golang nuts
邮件列表上搜索一个最近的线程,正好处理这个问题。@nicerobot,仅供参考,这是我提到的,特别是处理
加载*()
存储*()