在Go语言中,指针的赋值是原子操作吗?

huangapple go评论98阅读模式
英文:

Is assigning a pointer atomic in Go?

问题

在Go语言中,分配指针是原子操作吗?

在锁中是否需要分配指针?假设我只想将指针分配为nil,并希望其他线程能够看到它。我知道在Java中我们可以使用volatile来实现这一点,但在Go语言中没有volatile关键字。

英文:

Is assigning a pointer atomic in Go?

Do I need to assign a pointer in a lock? Suppose I just want to assign the pointer to nil, and would like other threads to be able to see it. I know in Java we can use volatile for this, but there is no volatile in Go.

答案1

得分: 20

在Go语言中,只有sync.atomic包中的操作是保证原子性的。

因此,如果你想确保原子性,你需要使用锁,比如sync.Mutex,或者使用原子操作。不过,我不建议使用原子操作,因为你将不得不在使用指针的每个地方都使用它们,而且很难正确使用。

使用互斥锁是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语言习惯的做法,但可能不完全适合你的应用程序。

更新

这里有一个示例,展示了如何使用atomic.SetPointer。由于使用了unsafe.Pointer,所以代码看起来有些丑陋。不过,unsafe.Pointer的类型转换在编译时不会产生额外的运行时开销。

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

type Struct struct {
    p unsafe.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))
}
英文:

The only things which are guaranteed to be atomic in go are the operations in sync.atomic.

So if you want to be certain you'll either need to take a lock, eg sync.Mutex or use one of the atomic primitives. I don't recommend using the atomic primitives though as you'll have to use them everywhere you use the pointer and they are difficult to get right.

Using the mutex is OK go style - you could define a function to return the current pointer with locking very easily, eg something like

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

These functions return a copy of the pointer to their clients which will stay constant even if the master pointer is changed. This may or may not be acceptable depending on how time critical your requirement is. It should be enough to avoid any undefined behaviour - the garbage collector will ensure that the pointers remain valid at all times even if the memory pointed to is no longer used by your program.

An alternative approach would be to only do the pointer access from one go routine and use channels to command that go routine into doing things. That would be considered more idiomatic go, but may not suit your application exactly.

Update

Here is an example showing how to use atomic.SetPointer. It is rather ugly due to the use of unsafe.Pointer. However unsafe.Pointer casts compile to nothing so the runtime cost is small.

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

}

答案2

得分: 5

根据规范,如果没有明确指定,你应该假设它不是原子的。即使它目前是原子的,也有可能在不违反规范的情况下发生变化。

英文:

Since the spec doesn't specify you should assume it is not. Even if it is currently atomic it's possible that it could change without ever violating the spec.

答案3

得分: 4

除了Nick的回答之外,自Go 1.4版本以来,还引入了atomic.Value类型。它的Store(interface)Load() interface方法负责处理unsafe.Pointer的转换。

简单的示例

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的文档中获取更详细的示例。

英文:

In addition to Nick's answer, since Go 1.4 there is atomic.Value type. Its Store(interface) and Load() interface methods take care of the unsafe.Pointer conversion.

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

Or a more extended example from the documentation on the Go playground.

答案4

得分: 2

自Go 1.19版本开始,atomic包中添加了atomic.Pointer

sync/atomic包定义了新的原子类型Bool、Int32、Int64、Uint32、Uint64、Uintptr和Pointer。这些类型隐藏了底层值,以便强制所有访问都使用原子API。Pointer类型还避免了在调用点处转换为unsafe.Pointer的需要。在结构体和分配的数据上,Int64和Uint64会自动对齐到64位边界,即使在32位系统上也是如此。

示例代码如下:

type ServerConn struct {
	Connection net.Conn
	ID string
}

func ShowConnection(p *atomic.Pointer[ServerConn]) {
	for {
		time.Sleep(10 * time.Second)
		fmt.Println(p, p.Load())
	}
}

func main() {
	c := make(chan bool)
	p := atomic.Pointer[ServerConn]{}
	s := ServerConn{ID: "first_conn"}
	p.Store(&s)
	go ShowConnection(&p)
	go func() {
		for {
			time.Sleep(13 * time.Second)
			newConn := ServerConn{ID: "new_conn"}
			p.Swap(&newConn)
		}
	}()
	<-c
}

你可以参考这个链接获取更多关于atomic.Pointer的示例。

英文:

Since Go 1.19 atomic.Pointer is added into atomic

> The sync/atomic package defines new atomic types Bool, Int32, Int64, Uint32, Uint64, Uintptr, and Pointer. These types hide the underlying values so that all accesses are forced to use the atomic APIs. Pointer also avoids the need to convert to unsafe.Pointer at call sites. Int64 and Uint64 are automatically aligned to 64-bit boundaries in structs and allocated data, even on 32-bit systems.

Sample

type ServerConn struct {
	Connection net.Conn
	ID string
}

func ShowConnection(p *atomic.Pointer[ServerConn]) {
	for {
		time.Sleep(10 * time.Second)
		fmt.Println(p, p.Load())
	}
}
func main() {
	c := make(chan bool)
	p := atomic.Pointer[ServerConn]{}
	s := ServerConn{ID: &quot;first_conn&quot;}
	p.Store(&amp;s)
	go ShowConnection(&amp;p)
	go func() {
		for {
			time.Sleep(13 * time.Second)
			newConn := ServerConn{ID: &quot;new_conn&quot;}
			p.Swap(&amp;newConn)
		}
	}()
	&lt;-c
}

答案5

得分: 1

请注意,原子性与“我只想将指针赋值为nil,并希望其他线程能够看到它”无关。后者的属性被称为可见性。

截至目前,对于前者的问题,Golang中的指针赋值(加载/存储)是原子的,这是由于更新的Go内存模型所决定的(https://go.dev/ref/mem)。

否则,对于一个不大于机器字长的内存位置x的读取r,必须观察到某个写入w,使得r不发生在w之前,并且没有写入w'使得w发生在w'之前且w'发生在r之前。也就是说,每次读取必须观察到由前一个或并发写入写入的值。

关于可见性,该问题没有足够的信息来具体回答。如果你只想知道是否可以安全地解引用指针,那么简单的加载/存储就足够了。然而,最有可能的情况是你想基于指针的空值与否来传递一些信息。这就需要你使用sync/atomic,它提供了同步能力。

英文:

Please note that atomicity has nothing to do with "I just want to assign the pointer to nil, and would like other threads to be able to see it". The latter property is called visibility.

The answer to the former, as of right now is yes, assigning (loading/storing) a pointer is atomic in Golang, this lies in the updated Go memory model

> Otherwise, a read r of a memory location x that is not larger than a machine word must observe some write w such that r does not happen before w and there is no write w' such that w happens before w' and w' happens before r. That is, each read must observe a value written by a preceding or concurrent write.

Regarding visibility, the question does not have enough information to be answered concretely. If you merely want to know if you can dereference the pointer safely, then a plain load/store would be enough. However, the most likely cases are that you want to communicate some information based on the nullness of the pointer. This requires you using sync/atomic, which provides synchronisation capabilities.

huangapple
  • 本文由 发表于 2014年1月30日 11:49:49
  • 转载请务必保留本文链接:https://go.coder-hub.com/21447463.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定