atomic pointer in Go 1-19

隨着 Go 1.19 的發佈,Go 語言團隊升級 sync/atomic, 在其中加入了 atomic.Pointer[T] 的新類型,也是第一個作爲全新 API 加入標準庫,支持泛型的數據類型,受到 Go 社區用戶的關注。同時,官方在 Release Notes 中,也提及到這個 API 的加入,使得原子值使用更加簡單。

特徵

atomic.Pointer 是泛型類。與 atomic.Value 不同的是,從 Value 類中拿出的數據,需要進行斷言,才能取出需要的值。而 Pointer 得益於泛型,直接能得到對應的數據類型。

type ServerConn struct {
	Connection net.Conn
	ID         string
	Open       bool
}


func main() {
	aPointer := atomic.Pointer[ServerConn]{}
	s := ServerConn{ID: "first_conn"}
	aPointer.Store(&s)
    
	fmt.Println(aPointer.Load())

	aValue := atomic.Value{}
	aValue.Store(&s)
	conn, ok := aValue.Load().(*ServerConn)
	if !ok {
		panic("assert is not ok")
	}
	fmt.Println(conn)
}

輸出:

&{<nil> first_conn false}
&{<nil> first_conn false}

實現比較

Value 的 Store 操作,會在運行時檢查、確定其存儲的實際類型,如果傳入的是 nil 值的接口類型 (any) 數據,或者與上次 Store 的數據類型不同,則會 panic。

func (v *Value) Store(val any) {
	if val == nil {
		panic("sync/atomic: store of nil value into Value")
	}
	vp := (*ifaceWords)(unsafe.Pointer(v))
	vlp := (*ifaceWords)(unsafe.Pointer(&val))
	for {
		typ := LoadPointer(&vp.typ)
		if typ == nil {
			// Attempt to start first store.
			// Disable preemption so that other goroutines can use
			// active spin wait to wait for completion.
			runtime_procPin()
			if !CompareAndSwapPointer(&vp.typ, nil, unsafe.Pointer(&firstStoreInProgress)) {
				runtime_procUnpin()
				continue
			}
			// Complete first store.
			StorePointer(&vp.data, vlp.data)
			StorePointer(&vp.typ, vlp.typ)
			runtime_procUnpin()
			return
		}
		if typ == unsafe.Pointer(&firstStoreInProgress) {
			// First store in progress. Wait.
			// Since we disable preemption around the first store,
			// we can wait with active spinning.
			continue
		}
		// First store completed. Check type and overwrite data.
		if typ != vlp.typ {
			panic("sync/atomic: store of inconsistently typed value into Value")
		}
		StorePointer(&vp.data, vlp.data)
		return
	}
}

// ifaceWords is interface{} internal representation.
type ifaceWords struct {
	typ  unsafe.Pointer
	data unsafe.Pointer
}

Pointer 的同樣操作,因會在編譯期確定類型,直接存儲指針即可。實現和調用簡單不少。

// Store atomically stores val into x.
func (x *Pointer[T]) Store(val *T) { StorePointer(&x.v, unsafe.Pointer(val)) }

數據競爭

以下代碼由於會在一定的時間內併發地讀寫,造成可能數據競爭。在 go run 命令中,加上 -race 參數,在代碼執行過程中,檢測可能會發生的數據競爭情況。

func ShowConnection(p *ServerConn) {
	for {
		time.Sleep(1 * time.Second)
		fmt.Println(p, *p)
	}

}
func main() {
	c := make(chan bool)
	p := ServerConn{ID: "first_conn"}
	go ShowConnection(&p)
	go func() {
		for {
			time.Sleep(3 * time.Second)
			newConn := ServerConn{ID: "new_conn"}
			p = newConn
		}
	}()
	<-c
}

運行輸出

go run -race ./main.go
&{<nil> first_conn false} {<nil> first_conn false}
&{<nil> first_conn false} {<nil> first_conn false}
==================
WARNING: DATA RACE
Write at 0x00c00013e870 by goroutine 8:
  main.main.func1()
      .../main.go:53 +0x6a

Previous read at 0x00c00013e870 by goroutine 7:
  runtime.convT()
     ../go/src/runtime/iface.go:321 +0x0
  main.ShowConnection()
      .../main.go:41 +0x64
  main.main.func2()
      .../main.go:48 +0x39

Goroutine 8 (running) created at:
  main.main()
      .../main.go:49 +0x16e

Goroutine 7 (running) created at:
  main.main()
      .../main.go:48 +0x104
==================
&{<nil> new_conn false} {<nil> new_conn false}
&{<nil> new_conn false} {<nil> new_conn false}

使用 atomic.Pointer

將上文的指針,換成 atomic.Pointer 後,執行時,沒有數據競爭的警告。

func ShowConnection(p *atomic.Pointer[ServerConn]) {
	for {
		time.Sleep(1 * time.Second)
		fmt.Println(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(3 * time.Second)
			newConn := ServerConn{ID: "new_conn"}
			p.Swap(&newConn)
		}
	}()
	<-c
}

輸出

go run -race .\main.go
&{<nil> first_conn false}
&{<nil> first_conn false}
&{<nil> new_conn false}
&{<nil> new_conn false}
&{<nil> new_conn false}

原子操作是互斥鎖以外,另一種操作共享資源的方法。atomic.Pointer 的加入,使得對指針類型數據的操作友好易用。 在不方便使用新特性的情況下,atomic.Value 仍然是對複合數據進行併發原子操作的好選擇。

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mizuame.moe/posts/atomic_pointer/