Search code examples
concurrencygoatomic

Is assigning a pointer atomic in Go?


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.


Solution

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