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

Go Proposal Weekly Digest

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

#63999accepted

sync/atomic: add Max/Min operators

ステータス変更: likely_accept accepted

要約

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

概要

sync/atomicパッケージに、値を比較して最大値・最小値をアトミックに更新するMax/Min操作を追加する提案です。ロックフリーなデータ構造の実装や並行処理での統計値収集を、より安全かつ効率的に行えるようにすることが目的です。

ステータス変更

likely_acceptaccepted
2026-07-08のレビューで「概ね良い提案」と評価され、返り値の仕様(更新前の値を返すべきか)について議論された上でlikely_acceptとなりました。1週間後の2026-07-15のレビューでも合意に変化がなかったため、そのままacceptedとなり、Issue自体は実装作業を追跡するトラッキングIssueに移行しました。

技術的背景

現状の問題点

現在、アトミックに最大値・最小値を更新するには、LoadCompareAndSwapを使ったCASループを自前で書く必要があります。

func atomicMax(addr *int64, val int64) {
    for {
        old := atomic.LoadInt64(addr)
        if val <= old || atomic.CompareAndSwapInt64(addr, old, val) {
            return
        }
    }
}

このパターンは冗長でインライン化されにくく、実際にGoランタイム自身のruntime/mgcsweep.goにも同様の処理を行うTODOコメントが残されていました。また、多くのCPUアーキテクチャ(RISC-VのAMOMAX/AMOMIN、Arm64のLDSMAX、PPCのUMAXなど)はハードウェアレベルでアトミックなmax/min命令をサポートしており、それを活用できていませんでした。

提案された解決策

sync/atomicに以下のAPIを追加します(int32/int64/uint32/uint64/uintptrの各型に対応)。

  • トップレベル関数: MaxInt32, MaxInt64, MaxUint32, MaxUint64, MaxUintptr(Min系も同様)
  • 各型(Int32, Int64, Uint32, Uint64, Uintptr)のメソッド: Max(val), Min(val)
    これは2023年に提案・採用されたAnd/Orビット演算子(Issue #61395)と対をなす提案であり、内部実装としてはruntime/internal/atomicMax, Max64, Maxuintptr, Min, Min64, Minuintptrを用意し、対応アーキテクチャでは専用命令を、それ以外ではCASループにフォールバックする形になる見込みです。競合検出器(race detector)についても、TSanにfetch_max/fetch_minが存在しないため別途実装が必要になる点が課題として挙げられています。

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

複数のゴルーチンが並行して動作する場面で、共有カウンタや統計値の最大・最小を安全かつ高速に更新できるようになります。

  • ロックフリーな共有データ構造の実装
  • データ並列処理における縮約(reduction)演算
  • 最適化処理において、これまでに到達した最良値(最大値)を記録し、非生産的なワーカースレッドの早期終了判定に利用する
  • 複数ワーカーが処理した入力の中で最大サイズのものを記録するなど、統計情報の収集

コード例

// Before: CASループによる手書きの実装
func recordMax(addr *int64, val int64) {
    for {
        old := atomic.LoadInt64(addr)
        if val <= old {
            return
        }
        if atomic.CompareAndSwapInt64(addr, old, val) {
            return
        }
    }
}
// After: 新APIを使った書き方
old := atomic.MaxInt64(addr, val)
// もしくは Int64 型のメソッドとして
var counter atomic.Int64
old := counter.Max(val)

議論のハイライト

  • 提案者は、Goランタイムのmgcsweep.goに残っていたTODOコメントをきっかけにこの提案を起こした。
  • 先行して採用されたAnd/Or演算子の提案(Issue #61395)と設計思想が共通しており、比較・参照される形で議論が進んだ。
  • アーキテクチャごとのハードウェアサポート状況が調査された(RISC-VのAMOMAX/AMOMIN、Arm64のLDSMAX、PPCのUMAX(符号なしのみ)、AMD64はRMW/CASループでの代替)。
  • レビュー会議では「戻り値は更新前(old)の値か、更新後(new)の値か」が焦点となった。ビット演算(And/Or)と同様に、oldからnewは計算できるがその逆はできないため、oldを返す方針で決着。実際にArm64/PPC/RISC-Vのハードウェア命令もいずれもoldを返し、CASループでもoldの取得は自然に行えるため、この設計が妥当と判断された。
  • 競合検出器(tsan)にfetch_max/fetch_min相当の機能がないため、実装時に別途対応が必要という技術的課題が指摘されている。

関連リンク