メインコンテンツへスキップ

Go Proposal Weekly Digest

Go言語のproposal更新を毎週お届け

#63999active

sync/atomic: add Max/Min operators

新規提案

要約

AIによる要約であり、誤りを含む場合があります。

概要

sync/atomic パッケージにアトミックなMax/Min演算を追加するproposal。複数のgoroutineが共有する数値カウンタに対して、ロックなしで最大値・最小値を安全に更新するためのプリミティブを提供する。

ステータス変更

(なし)active
2026年6月17日の週次Proposal Review Meetingにて、sync/atomic: add Max/Min operators が初めてアクティブ列に追加された。2023年11月に提案されてから約2年半が経過しており、類似提案である sync/atomic へのAnd/Or演算子追加(#61395)が先に承認・実装されたことで、Max/Minも自然な次のステップとして議論のテーブルに乗ったと考えられる。

技術的背景

現状の問題点

並行プログラムで「複数スレッドが競合する最大値カウンタ」を安全に更新したい場面は多い。現在はCompare-And-Swap(CAS)ループで自前実装するしかない。Goランタイムのガベージコレクタ内(runtime/mgcsweep.go)でも同様のパターンが使われており、TODOコメントで「atomic maxがあれば共通化できるのに」と言及されていた。

// 現状のワークアラウンド: CASループ
func atomicMax(addr *int64, val int64) int64 {
    for {
        old := atomic.LoadInt64(addr)
        if old >= val {
            return old
        }
        if atomic.CompareAndSwapInt64(addr, old, val) {
            return old
        }
    }
}

提案された解決策

sync/atomic パッケージに以下のAPIを追加する。

// スタンドアロン関数
func MaxInt32(addr *int32, val int32) (old int32)
func MaxInt64(addr *int64, val int64) (old int64)
func MaxUint32(addr *uint32, val uint32) (old uint32)
func MaxUint64(addr *uint64, val uint64) (old uint64)
func MaxUintptr(addr *uintptr, val uintptr) (old uintptr)
func MinInt32(addr *int32, val int32) (old int32)
// ... 以下同様
// 型付きアトミック変数のメソッド
func (*Int32) Max(val int32) (old int32)
func (*Int64) Max(val int64) (old int64)
func (*Uint32) Max(val uint32) (old uint32)
func (*Uint64) Max(val uint64) (old uint64)
func (*Uintptr) Max(val uintptr) (old uintptr)
func (*Int32) Min(val int32) (old int32)
// ... 以下同様

戻り値は更新前の値(old)。アーキテクチャによってはネイティブ命令で実装できる。

  • RISC-V: AMOMAX / AMOMIN
  • Arm64: LDSMAX など
  • AMD64: ネイティブ命令なし、CASループにフォールバック
    runtime/internal/atomic にも同等の低レベル関数(MaxMax64MaxUintptr 他)が必要になる。

これによって何ができるようになるか

並行処理における最大値・最小値の管理が、ロック不要かつ簡潔に書けるようになる。

コード例

// Before: CASループによる手動実装
var maxSeen int64
func recordValue(v int64) {
    for {
        old := atomic.LoadInt64(&maxSeen)
        if old >= v {
            break
        }
        if atomic.CompareAndSwapInt64(&maxSeen, old, v) {
            break
        }
    }
}
// After: 新APIを使った書き方
var maxSeen atomic.Int64
func recordValue(v int64) {
    maxSeen.Max(v)
}

主なユースケース:

  1. 統計収集: 複数ワーカーが処理した入力の最大値・最小値をゴルーチンセーフに記録する
  2. 最適化の進捗管理: 最適化アルゴリズムで「これ以上改善が見込めないスレッドを終了させる」ためのカットオフ値共有
  3. GCや低レベルランタイム: ランタイム内の単調増加カウンタの更新(実際にGoランタイム内に需要がある)

議論のハイライト

  • ハードウェアサポートの非対称性: Arm64・RISC-Vはネイティブ命令が存在するが、AMD64はCASループになる。それでも標準ライブラリに持つ価値はあるという判断
  • And/Or演算子との一貫性: 同様の動機で承認された #61395(And/Or追加)との整合性が明確に示されており、本proposalもその自然な拡張として位置づけられている
  • レースディテクタの対応: TSANには fetch_max / fetch_min が存在しないため、LLVMアップストリームへの対応追加が必要。提案者(@mauri870)は自ら対応を申し出ている
  • スタンドアロン関数と型付きメソッドの両方を追加: 既存のAnd/Or追加と同様のパターンで、atomic.Int32 等の型メソッドと生ポインタ版の両方を提供する設計
  • ランタイム内需要が動機: 外部ユーザーの要望だけでなく、Goランタイム自身が必要としているという点が説得力を持つ

関連リンク