Concurrency 在Go中分配指针是原子的吗?

Concurrency 在Go中分配指针是原子的吗?,concurrency,go,atomic,Concurrency,Go,Atomic,在Go中分配指针是原子的吗 我需要在锁中指定指针吗?假设我只想将指针指定给nil,并希望其他线程能够看到它。我知道在Java中我们可以使用volatile,但是Go中没有volatile。因为规范没有指定,所以您应该假设它不是。即使它当前是原子的,也有可能在不违反规范的情况下进行更改。保证在go中是原子的唯一东西是在go中的操作 所以,如果你想确定,你要么需要一个锁,例如,或者使用一个原子原语。但是我不建议使用原子原语,因为在使用指针的任何地方都必须使用它们,而且它们很难正确使用 使用互斥是OK

在Go中分配指针是原子的吗


我需要在锁中指定指针吗?假设我只想将指针指定给nil,并希望其他线程能够看到它。我知道在Java中我们可以使用volatile,但是Go中没有volatile。

因为规范没有指定,所以您应该假设它不是。即使它当前是原子的,也有可能在不违反规范的情况下进行更改。

保证在go中是原子的唯一东西是在go中的操作

所以,如果你想确定,你要么需要一个锁,例如,或者使用一个原子原语。但是我不建议使用原子原语,因为在使用指针的任何地方都必须使用它们,而且它们很难正确使用

使用互斥是OK go风格的-您可以定义一个函数来返回带有锁定的当前指针,例如

import "sync"

var secretPointer *int
var pointerLock sync.Mutex

func CurrentPointer() *int {
    pointerLock.Lock()
    defer pointerLock.Unlock()
    return secretPointer
}

func SetPointer(p *int) {
    pointerLock.Lock()
    secretPointer = p
    pointerLock.Unlock()
}
这些函数将指针的副本返回给它们的客户机,即使主指针发生更改,该副本也将保持不变。这可能是可接受的,也可能是不可接受的,这取决于您的需求对时间的要求。这应该足以避免任何未定义的行为——垃圾收集器将确保指针始终有效,即使程序不再使用指向的内存

另一种方法是只从一个go例程进行指针访问,并使用通道命令这些go例程进行操作。这将被认为是更惯用的go,但可能并不完全适合您的应用程序

更新

显示如何使用
原子.SetPointer
。由于使用了
不安全的指针,它相当难看。但是
unsafe.Pointer
将编译转换为空,因此运行时成本很小

import (
    "fmt"
    "sync/atomic"
    "unsafe"
)

type Struct struct {
    p unsafe.Pointer // some pointer
}

func main() {
    data := 1

    info := Struct{p: unsafe.Pointer(&data)}

    fmt.Printf("info is %d\n", *(*int)(info.p))

    otherData := 2

    atomic.StorePointer(&info.p, unsafe.Pointer(&otherData))

    fmt.Printf("info is %d\n", *(*int)(info.p))

}
除此之外,由于Go 1.4还有类型。它的
Store(interface)
Load()interface
方法负责
不安全的指针转换

简单:


或者是。

上的文档中的一个更扩展的示例,我确实查看了atomic包,但没有看到用于存储指向结构的指针的原子方法。方法StorePointer看起来不正确。func StorePointer(addr*unsafe.Pointer,val unsafe.Pointer)已使用
StorePointer
示例更新了答案。@NickCraig-Wood在使用
atomic.StorePointer
时,是否需要使用
atomic.ReadPointer
来读取它,还是常规的非原子读取可以?在
atomic.StorePointer
中使用的存储屏障应该会阻止并发读取,因此在这种情况下使用
atomic.ReadPointer
对我来说是无用的。是这样吗?您确定第一个示例适用于更广泛的环境:执行
SetPointer
的线程可能需要在发布新指针值之前填充指向的值。从读取器线程的角度来看,我在中没有看到任何东西证明写入
*secretPointer
发生在写入
secretPointer
之前。进一步思考:使用互斥体,根据互斥体规则,读取
secretPointer
发生在写入之后。由于内部goroutine程序顺序,
*secretPointer
的写入发生在
secretPointer
的写入之前。这些都足以让它工作。你可能会觉得有趣。你也可能会觉得阅读文档很有趣——它定义了Go实现的需求。顺便说一句,它没有告诉任何关于原语类型的加载/存储操作的原子性的事情,只提到了它们的可观察顺序。
package main

import (
    "sync/atomic"
)

type stats struct{}

type myType struct {
    stats atomic.Value
}

func main() {
    var t myType
    
    s := new(stats)
    
    t.stats.Store(s)
    
    s = t.stats.Load().(*stats)
}