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

50pmv0ei  于 2022-12-16  发布在  Go
关注(0)|答案(3)|浏览(155)

假设我有这个:

go func() {
    for range time.Tick(1 * time.Millisecond) {
        a, b = b, a
    }
}()

以及其他地方:

i := a // <-- Is this safe?

对于这个问题,i的值相对于原始的ab是什么并不重要,唯一的问题是阅读a是否安全,也就是说,a是否可能是nil、部分赋值、无效、未定义......除了有效值之外的任何值?
I've tried to make it fail但到目前为止它总是成功的(在我的Mac上)。
在Go内存模型文档中,除了这句话之外,我找不到任何具体的东西:
对大于单个机器字的值的读写行为就像按未指定顺序执行的多个机器字大小的操作。
这是不是意味着一个机器字的写操作是原子的呢?如果是,那么Go语言中的函数指针写操作是一个机器字操作吗?

**更新:**以下是a properly synchronized solution

wfsdck30

wfsdck301#

The Go Memory Model中,对来自多个goroutine的任何变量的非同步、并发访问(其中至少一个是write)是未定义的行为

Undefined的意思是:undefined。可能你的程序会正常工作,也可能会不正常工作。它可能会导致内存丢失和Go语言运行时提供的类型安全(见下面的例子)。它甚至可能使你的程序崩溃。或者它甚至可能导致地球爆炸(这种可能性非常小,可能甚至小于1 e-40,但仍然...)。

这里的 undefined 意味着是的,i可以是nil、部分赋值、无效、未定义......除了ab之外的任何东西。
不要再认为一些数据竞争是(或可能是)良性或无害的,如果不加以注意,它们可能是最糟糕的事情的根源。
因为你的代码在一个goroutine中写入变量a,在另一个goroutine中读取它(它试图将其值赋给另一个变量i),这是一个数据竞赛,因此是不安全的。在测试中它是否“正确”工作并不重要。可以将您的代码作为起点,扩展/建立在它之上,并由于最初“无害”的数据竞争而导致灾难。
作为相关问题,请阅读GolangMap对于并发读/写操作的安全性如何?和go lang中的不正确同步。
强烈建议阅读德米特里Vyukov的博客文章:Benign data races: what could possibly go wrong?
还有一篇非常有趣的博客文章,展示了一个通过故意数据竞争破坏Go语言内存安全的例子:Golang data races to break memory safety

yvgpqqbh

yvgpqqbh2#

Race condition而言,这是不安全的。简而言之,我对争用条件的理解是当存在多个异步例程时(协程,线程,进程,goroutine等)试图访问同一个资源,并且至少有一个是写操作,所以在你的例子中我们有2个goroutine阅读function类型的变量,我认为从并发的Angular 来看,重要的是这些变量在某个地方有内存空间,我们试图在内存的那个部分读写。

  • 简短回答 *:只要运行您的示例,将-race标志与go run -racego build -race一起使用,您就会看到检测到的数据竞争
6l7fqoea

6l7fqoea3#

到目前为止,您的问题的答案是,如果ab不大于一个机器字,则i必须等于ab,否则,它可能包含一个未指定的值,很可能是ab不同部分的交错。
Go memory model在2022年6月6日的版本中保证,如果程序执行争用条件,则对不大于机器字的位置的内存访问必须是原子的。
否则,不大于机器字的存储器位置x的读取r必须观察到某个写入w,使得r不在w之前发生,并且没有写入w“,使得w在w”之前发生,并且w“在r之前发生。即,每个读取必须观察到由先前或并发写入所写入的值。
这里的happen-before关系在上一节的内存模型中定义。
从更大内存位置读取的结果是未指定的,但绝对不像在C++领域中那样未定义。
鼓励但不要求大于单个机器字的存储器位置的读取满足与字大小的存储器位置相同的语义,观察单个允许的写入w。出于性能原因,实施方案可改为将较大操作视为一组以未指定次序的个别机器字大小的操作。这意味着多字数据结构上的争用可能导致不对应于单个写入的不一致值。(指针,长度)或(指针,类型)对,就像大多数Go语言实现中的接口值、Map、切片和字符串一样,这样的竞争反过来会导致任意的内存破坏。

相关问题