こんにちは、GoAdventCalendarの記事を書き忘れておりましたので急いで書きました。
atomic.Valueとは
こちらGo 1.4より追加されました。
Goの公式ドキュメントでは Do not communicate by sharing memory.
とよく書かれていますが、複数のGoroutineからひとつの変数を参照・更新するというのは標準パッケージを眺めていても割と頻繁に登場します。
その際にsyncパッケージ等を使い値のrace conditionを解消するのですが、共有する変数への変更処理(mapに新しい値を追加する、など)をよりatomicな形で提供するのがatomic.Valueになります。
使い方はatomic.ValueのExampleを見てみて下さい。
実装を見てみる
では何故こういったパッケージを利用する必要があるのか内部実装を見ながら考えてみよう、と思いたったので読んでみます。
package atomic
import (
"unsafe"
)
type Value struct {
v interface{}
}
このへんは普通の宣言になりますね。
// interface{}の内部的な表現、形と値それぞれのポインタを持つ
type ifaceWords struct {
typ unsafe.Pointer
data unsafe.Pointer
}
上記を見ると、形と値をそれぞれ別のポインタとして保持しています。
Goのinterface{}は2つ分のwordを一方は形、一方は指し示す値として別々に保持しているため、このような形が必要になります。
参考: InterfaceSlice https://github.com/golang/go/wiki/InterfaceSlice
// 値の読み込み
func (v *Value) Load() (x interface{}) {
vp := (*ifaceWords)(unsafe.Pointer(v))
typ := LoadPointer(&vp.typ)
if typ == nil || uintptr(typ) == ^uintptr(0) {
// 型のポインタが定義されていない場合、最初のデータ追加が終わっていないとみなす。
return nil
}
data := LoadPointer(&vp.data)
// 返り値xのポインタに対してinterfaceの型情報と値情報のポインタを設定する
xp := (*ifaceWords)(unsafe.Pointer(&x))
xp.typ = typ
xp.data = data
return
}
上記ではatomic.LoadPointerが多用されています。
データをatomic.Valueが保持している場合、値のポインタをatomicに読み込み、返り値に渡しています。
// 値の追加
func (v *Value) Store(x interface{}) {
if x == nil {
panic("sync/atomic: store of nil value into Value")
}
vp := (*ifaceWords)(unsafe.Pointer(v))
xp := (*ifaceWords)(unsafe.Pointer(&x))
for {
typ := LoadPointer(&vp.typ)
if typ == nil {
runtime_procPin() // これを呼ぶことでコンテキストスイッチを停止し、他のgoroutineなどを待たせる
// typのポインタに初期書き込み中の旨を割り当てる
if !CompareAndSwapPointer(&vp.typ, nil, unsafe.Pointer(^uintptr(0))) {
runtime_procUnpin()
continue
}
// 最初の書き込み処理をポインタに対して実施する。
StorePointer(&vp.data, xp.data)
StorePointer(&vp.typ, xp.typ)
runtime_procUnpin()
return
}
if uintptr(typ) == ^uintptr(0) {
// typのポインタが上記の書き込み処理中のflagと同値なら先頭に戻る
continue
}
// 違う型を見ている場合はエラー
if typ != xp.typ {
panic("sync/atomic: store of inconsistently typed value into Value")
}
StorePointer(&vp.data, xp.data)
return
}
}
書き込み時には、コンテキストスイッチを停止し、初期書き込み中である旨を^uintptr(0)
という値をフラグとしてtypに渡し、その後形と値それぞれのポインタを入れ替えています。
func runtime_procPin()
func runtime_procUnpin()
これを宣言しておくと、runtime側でコンテキストスイッチを停止・再開可能なようです。
こちらの資料なども参考にすると、interfaceのpointer入れ替え処理はwordサイズを超えるnon-atomicな処理であるので、コンテキストスイッチを停止しつつ値を書き換え、読み込み中である旨を明示するなど、単なるポインタの入れ替え処理をしようとするにも、様々な注意が必要なようです。
最後に
Global変数を各所で見て回るというのを自分で安全に実装しようとすると、unsafeパッケージで面倒な実装をすることになるのでatomicパッケージをうまく使うか、channelに頼りましょう。
三ʕ◔ϖ◔ʔ
その他参考
Go memory specification https://golang.org/ref/mem
Go FAQ https://golang.org/doc/faq#What_operations_are_atomic_What_about_mutexes
http://play.golang.org/p/OrJvbN4iYK