【问题标题】:Is assigning a pointer atomic in Go?在 Go 中分配指针是原子的吗?
【发布时间】:2014-02-22 05:21:09
【问题描述】:

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

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

【问题讨论】:

  • 你可能会觉得this thread很有趣。
  • 您可能还会发现阅读"Go memory model" 文档很有趣——它定义了 Go 实现的要求。 AFAIK,它没有说明原始类型的加载/存储操作的原子性,而只谈到了它们的可观察顺序。

标签: concurrency go atomic


【解决方案1】:

由于规范没有指定,你应该假设它不是。即使它目前是原子的,它也有可能在不违反规范的情况下进行更改。

【讨论】:

    【解决方案2】:

    在 go 中唯一保证是原子的就是 sync.atomic 中的操作。

    因此,如果您想确定要么需要锁定,例如sync.Mutex,要么使用原子原语之一。我不建议使用原子原语,因为您必须在使用指针的任何地方都使用它们,而且它们很难正确使用。

    使用互斥锁是可以的——你可以定义一个函数来很容易地返回当前指针并锁定,例如

    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()
    }
    

    这些函数将指针的副本返回给它们的客户端,即使主指针发生更改,该副本也将保持不变。这可能会也可能不会接受,具体取决于您的要求对时间的要求。避免任何未定义的行为就足够了——垃圾收集器将确保指针始终保持有效,即使您的程序不再使用指向的内存。

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

    更新

    Here is an example 展示了如何使用atomic.SetPointer。由于使用了unsafe.Pointer,它相当难看。但是 unsafe.Pointer 将 compile 强制转换为空,因此运行时成本很小。

    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))
    
    }
    

    【讨论】:

    • 我确实看过 atomic 包,但没有看到原子存储指向结构的指针的方法。 StorePointer 方法看起来不正确。 func StorePointer(addr *unsafe.Pointer, val unsafe.Pointer)
    • 已用StorePointer 示例更新了答案。
    • @NickCraig-Wood 使用atomic.StorePointer 时,是否必须使用atomic.ReadPointer 才能读取它,或者常规的非原子读取可以吗? atomic.StorePointer 中使用的存储屏障应该阻止并发读取,因此在这种情况下使用 atomic.ReadPointer 对我来说听起来毫无用处。对吗?
    • 您确定第一个示例可以在更广泛的上下文中工作吗:执行SetPointer 的线程可能需要在发布新指针值之前填充指向的值。从读者线程的角度来看,我在memory model 中没有看到任何确定对*secretPointer 的写入发生在secretPointer 的写入之前。
    • 进一步冥想:使用互斥锁,secretPointer 的读取发生在凭借互斥锁规则写入之后。 *secretPointer 的写入发生在secretPointer 的写入之前,这是由于协程内程序顺序。这些足以让它发挥作用。
    【解决方案3】:

    除了Nick's answer,从Go 1.4开始还有atomic.Value类型。它的Store(interface)Load() interface 方法负责unsafe.Pointer 转换。

    简单example:

    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)
    }
    

    或来自Go playground 文档的更扩展示例。

    【讨论】:

      猜你喜欢
      • 2019-08-28
      • 1970-01-01
      • 2022-10-04
      • 1970-01-01
      • 1970-01-01
      • 2018-12-08
      • 1970-01-01
      • 2020-11-23
      相关资源
      最近更新 更多