Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

[インデックス 13131] ファイルの概要

このコミットは、Go言語のsync/atomicパッケージにおける64ビットアトミック操作(LoadStoreAdd)のLinux/ARMアーキテクチャ向け実装に関するものです。特に、ARMv5アーキテクチャ上でGOARM=5が設定された環境で、これらの操作が正しく機能するように改善することを目的としています。変更の核心は、CompareAndSwapUint64(64ビットの比較と交換)操作を基盤として、他の64ビットアトミック操作をソフトウェア的に実装することにあります。

コミット

commit 090f9fc3ef532597c33f0702dfd541ca95529efd
Author: Shenghou Ma <minux.ma@gmail.com>
Date:   Wed May 23 02:02:01 2012 +0800

    sync/atomic: use cas64 to implement {Load,Store,Add}{Uint,Int}64 on Linux/ARM
            Now with GOARM=5 our all.bash should pass on ARMv5 systems.
            Fixes #3331.
    
    R=golang-dev, rsc, dvyukov
    CC=golang-dev
    https://golang.org/cl/6210071

GitHub上でのコミットページへのリンク

https://github.com/golang/go/commit/090f9fc3ef532597c33f0702dfd541ca95529efd

元コミット内容

sync/atomic: use cas64 to implement {Load,Store,Add}{Uint,Int}64 on Linux/ARM
        Now with GOARM=5 our all.bash should pass on ARMv5 systems.
        Fixes #3331.

変更の背景

このコミットの主な背景は、Go言語がARMv5アーキテクチャ上で64ビットのアトミック操作を正しくサポートしていなかった問題にあります。Goのテストスイートであるall.bashが、GOARM=5(ARMv5アーキテクチャをターゲットとする設定)の環境で失敗するというバグ(Issue #3331)が報告されていました。

ARMv5のような古いARMアーキテクチャでは、64ビットの値を単一の命令でアトミックに操作するための専用ハードウェア命令が不足しているか、効率的ではありませんでした。特に、CompareAndSwap(CAS)のような複合的なアトミック操作は、複数の命令に分解される可能性があり、その間に他のプロセッサやコアがメモリにアクセスすることで競合状態が発生し、操作の原子性が保証されない問題がありました。

この問題は、並行処理において共有メモリ上のデータを安全に操作するために不可欠なアトミック操作の信頼性を損なうものであり、GoプログラムがARMv5環境で正しく動作しない原因となっていました。このコミットは、この根本的な問題を解決し、ARMv5上でのGoの安定性と互換性を向上させることを目的としています。

前提知識の解説

1. アトミック操作 (Atomic Operations)

アトミック操作とは、複数のCPUコアやスレッドから同時にアクセスされた場合でも、その操作全体が不可分(分割不可能)であることを保証する操作です。つまり、操作の途中で他の操作が割り込むことがなく、常に一貫した状態を保ちます。これは、並行プログラミングにおいて共有データの一貫性を保ち、競合状態(Race Condition)を防ぐために非常に重要です。

一般的なアトミック操作には以下のようなものがあります。

  • アトミックロード (Atomic Load): メモリから値を読み込む操作がアトミックに行われることを保証します。
  • アトミックストア (Atomic Store): メモリに値を書き込む操作がアトミックに行われることを保証します。
  • 比較と交換 (Compare-and-Swap, CAS): 特定のメモリ位置の値が期待する値と一致する場合にのみ、その値を新しい値に更新する操作です。この操作全体がアトミックに行われます。CASは、ロックフリーなデータ構造やアルゴリズムを実装するための基本的なプリミティブとして広く使用されます。
  • アトミック加算 (Atomic Add): メモリ上の値に指定された値をアトミックに加算します。

2. ARMアーキテクチャと64ビット操作

ARM(Advanced RISC Machine)は、モバイルデバイスや組み込みシステムで広く使用されているCPUアーキテクチャです。ARMアーキテクチャには多くのバージョンがあり、それぞれ異なる命令セットや機能を持っています。

  • ARMv5: 比較的に古いARMアーキテクチャのバージョンです。このバージョンでは、64ビットの整数値を直接扱うための効率的なハードウェアサポートが限られている場合があります。特に、64ビットのCASのような複合的なアトミック操作は、単一のCPU命令として提供されていないことが多く、複数の32ビット命令の組み合わせでエミュレートする必要がありました。このエミュレーションは、原子性を保証するために特別な注意(例えば、カーネルレベルのヘルパー関数や、スピンロックのようなソフトウェア的な同期メカニズム)を必要とします。

3. Go言語のsync/atomicパッケージ

Go言語のsync/atomicパッケージは、低レベルのアトミック操作を提供します。これにより、ミューテックス(sync.Mutex)のようなロックメカニズムを使用せずに、共有変数を安全に操作できます。ロックを使用しないため、特定のシナリオではより高いパフォーマンスを実現できますが、正しく使用しないとデッドロックや競合状態を引き起こす可能性があります。

sync/atomicパッケージは、CPUが提供するアトミック命令を最大限に活用するように設計されています。しかし、特定のアーキテクチャ(この場合はARMv5)で必要なアトミック命令が直接利用できない場合、Goランタイムはソフトウェア的なフォールバックや、より基本的なアトミックプリミティブ(例えば、32ビットCAS)を組み合わせて64ビット操作をエミュレートする必要があります。

4. GOARM環境変数

GOARMはGoコンパイラがARMアーキテクチャ向けにコードを生成する際に使用する環境変数です。この変数は、ターゲットとするARMプロセッサのバージョン(例: ARMv5、ARMv6、ARMv7)を指定します。GOARM=5は、GoコンパイラがARMv5アーキテクチャに最適化されたコードを生成するように指示します。ARMv5は浮動小数点演算ユニット(FPU)を持たないことが多いため、この設定は特に重要です。

5. all.bash

all.bashはGo言語のソースツリーに含まれるスクリプトで、Goのビルド、テスト、およびインストールプロセス全体を実行します。これは、Goの変更が様々なプラットフォームやアーキテクチャで正しく機能するかどうかを確認するための主要なテストスイートです。all.bashが特定の環境で失敗するということは、その環境でのGoの動作に問題があることを示します。

技術的詳細

このコミットの技術的詳細は、ARMv5アーキテクチャにおける64ビットアトミック操作の課題と、それをGo言語のsync/atomicパッケージでどのように解決したかに集約されます。

ARMv5は32ビットアーキテクチャであり、64ビットの値をレジスタに一度にロードしたり、単一の命令で操作したりすることができません。そのため、64ビットの値を扱う際には、2つの32ビットレジスタに分割して処理する必要があります。この分割処理が、アトミック操作の実現を困難にします。

従来のARMv5では、64ビットのアトミック操作(特にCAS)をハードウェアで直接サポートする命令がありませんでした。そのため、Goのランタイムは、これらの操作をソフトウェア的にエミュレートする必要がありました。しかし、単純なソフトウェアエミュレーションでは、複数の命令に分割された操作の途中でコンテキストスイッチや割り込みが発生し、原子性が破られる可能性がありました。

このコミットでは、以下の戦略を採用しています。

  1. CompareAndSwapUint64を基盤とする: ARMv5上で64ビットのCompareAndSwapcas64)を正しく実装することが、他の64ビットアトミック操作を実現するための鍵となります。このcas64は、おそらくカーネルのヘルパー関数や、より低レベルなアセンブリ言語のプリミティブ(例えば、LDREX/STREXのような排他ロード/ストア命令の組み合わせ、または古いARMではスピンロックを用いたソフトウェア的な実装)を利用して、原子性を保証するように実装されています。コミットメッセージのuse cas64という記述から、このcas64がARMv5上で64ビットのCASをアトミックに実行できる何らかのメカニズムを指していることがわかります。

  2. 高レベルなアトミック操作のソフトウェア実装: LoadUint64StoreUint64AddUint64といった高レベルな64ビットアトミック操作は、新しく追加されたsrc/pkg/sync/atomic/64bit_linux_arm.goファイル内でGo言語によって実装されています。これらの実装は、CompareAndSwapUint64を繰り返し呼び出すループ(スピンロックに似たメカニズム)を使用しています。

    • loadUint64(addr *uint64): この関数は、addrが指す64ビットの値をアトミックに読み込みます。実装は、無限ループ内で*addrの現在の値を読み込み、その値を使ってCompareAndSwapUint64(addr, val, val)を実行します。CompareAndSwapUint64は、addrの値がvalと一致する場合にのみvalvalに書き換える(つまり、何も変更しない)操作ですが、この操作が成功したということは、読み込んだvalがその時点での最新の値であり、他のゴルーチンによって変更されていないことを意味します。これにより、読み込み操作の原子性が保証されます。

    • storeUint64(addr *uint64, val uint64): この関数は、addrが指す64ビットのメモリ位置にvalをアトミックに書き込みます。実装は、無限ループ内で*addrの現在の値(old)を読み込み、CompareAndSwapUint64(addr, old, val)を実行します。このCAS操作が成功した場合、oldの値がvalにアトミックに更新されたことになり、ループを抜けます。失敗した場合は、他のゴルーチンがaddrの値を変更したことを意味するため、ループを続行して再試行します。

    • addUint64(val *uint64, delta uint64): この関数は、valが指す64ビットの値にdeltaをアトミックに加算します。実装は、無限ループ内で*valの現在の値(old)を読み込み、old + deltaを計算して新しい値(new)とします。そして、CompareAndSwapUint64(val, old, new)を実行します。CASが成功すれば、加算がアトミックに行われたことになり、ループを抜けます。失敗した場合は再試行します。

このアプローチにより、ARMv5のようなハードウェアアトミック命令が限られた環境でも、基本的なCompareAndSwapUint64が正しく機能していれば、他の複雑なアトミック操作もソフトウェア的に安全に実現できるようになります。これにより、GOARM=5環境でのall.bashのテストがパスするようになり、GoのARMv5サポートが改善されました。

コアとなるコードの変更箇所

このコミットでは、主に以下の2つのファイルが変更されています。

  1. src/pkg/sync/atomic/64bit_linux_arm.go (新規追加)

    • このファイルは、Go言語でloadUint64storeUint64addUint64の3つの関数を新しく定義しています。これらの関数は、CompareAndSwapUint64を内部的に使用して、64ビットのアトミックな読み込み、書き込み、加算操作を実現します。
  2. src/pkg/sync/atomic/asm_linux_arm.s (修正)

    • このファイルは、ARMアーキテクチャ向けのアセンブリ言語で書かれたアトミック操作の実装を含んでいます。
    • CompareAndSwapInt64CompareAndSwapUint64の定義が修正され、CompareAndSwapUint64が主要なCAS64実装として機能するように調整されています。
    • AddInt64, AddUint64, LoadInt64, LoadUint64, StoreInt64, StoreUint64といったGoの公開APIに対応するアセンブリ関数が、新しくGoで実装されたloadUint64, storeUint64, addUint64関数(または既存のCompareAndSwapUint64)に分岐(B命令)するように変更されています。これは、これらの操作がアセンブリレベルで直接実装されるのではなく、Goレベルのラッパー関数を通じてCompareAndSwapUint64を利用するようになったことを意味します。

コアとなるコードの解説

src/pkg/sync/atomic/64bit_linux_arm.go

// Copyright 2012 The Go Authors.  All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package atomic

func loadUint64(addr *uint64) (val uint64) {
	for {
		val = *addr // 現在の値を読み込む
		// CompareAndSwapUint64が成功すれば、読み込んだvalが最新であることを保証
		if CompareAndSwapUint64(addr, val, val) {
			break // 成功したらループを抜ける
		}
	}
	return
}

func storeUint64(addr *uint64, val uint64) {
	for {
		old := *addr // 現在の値を読み込む
		// oldが変更されていなければ、oldをvalにアトミックに更新
		if CompareAndSwapUint64(addr, old, val) {
			break // 成功したらループを抜ける
		}
	}
	return
}

func addUint64(val *uint64, delta uint64) (new uint64) {
	for {
		old := *val // 現在の値を読み込む
		new = old + delta // 新しい値を計算
		// oldが変更されていなければ、oldをnewにアトミックに更新
		if CompareAndSwapUint64(val, old, new) {
			break // 成功したらループを抜ける
		}
	}
	return
}

このGoコードは、64ビットのLoadStoreAdd操作を、より基本的なCompareAndSwapUint64プリミティブを使って実装しています。これは、ハードウェアが直接これらの操作をサポートしない場合に、ソフトウェア的に原子性を保証するための一般的なパターンです。ループ内で現在の値を読み込み、その値が変更されていないことを確認しながらCAS操作を試みます。もしCASが失敗した場合(つまり、他のゴルーチンが値を変更した)、操作を再試行します。

src/pkg/sync/atomic/asm_linux_arm.s

// ... (既存のコード) ...

TEXT ·CompareAndSwapInt64(SB),7,$0
	B   	·CompareAndSwapUint64(SB) // Int64のCASをUint64のCASにリダイレクト

TEXT ·CompareAndSwapUint64(SB),7,$-4
	MOVW	armCAS64(SB), R0
	CMP 	$0, R0
	MOVW.NE	R0, PC
	B		setupAndCallCAS64<>(SB) // CAS64の実際の呼び出しロジック

TEXT ·AddInt64(SB),7,$0
	B	·addUint64(SB) // Int64のAddをGoで実装されたaddUint64にリダイレクト

TEXT ·AddUint64(SB),7,$0
	B	·addUint64(SB) // Uint64のAddをGoで実装されたaddUint64にリダイレクト

TEXT ·LoadInt64(SB),7,$0
	B	·loadUint64(SB) // Int64のLoadをGoで実装されたloadUint64にリダイレクト

TEXT ·LoadUint64(SB),7,$0
	B	·loadUint64(SB) // Uint64のLoadをGoで実装されたloadUint64にリダイレクト

TEXT ·StoreInt64(SB),7,$0
	B	·storeUint64(SB) // Int64のStoreをGoで実装されたstoreUint64にリダイレクト

TEXT ·StoreUint64(SB),7,$0
	B	·storeUint64(SB) // Uint64のStoreをGoで実装されたstoreUint64にリダイレクト

// ... (既存のコード) ...

このアセンブリコードは、Goのsync/atomicパッケージが提供する公開関数(例: AddInt64LoadUint64など)が、内部的にどの実装にルーティングされるかを定義しています。変更前は、これらの関数はそれぞれ独自のアセンブリ実装(例: armAddUint64)に分岐していましたが、このコミットにより、新しくGoで実装されたloadUint64storeUint64addUint64関数、または主要なCompareAndSwapUint64関数に分岐するように変更されました。

これは、ARMv5上で64ビットアトミック操作の基盤としてCompareAndSwapUint64を確立し、それ以外の操作はCompareAndSwapUint64を安全に利用するGoコードで実装するという設計思想を反映しています。これにより、アセンブリコードの複雑さを軽減しつつ、異なるARMバージョン間での互換性と保守性を向上させています。

関連リンク

  • Go Issue #3331: https://github.com/golang/go/issues/3331
    • このコミットが修正したバグトラッカーのIssueです。ARMv5上でall.bashが失敗する問題について議論されています。
  • Gerrit Change-ID 6210071: https://golang.org/cl/6210071
    • このコミットに対応するGoのコードレビューシステム(Gerrit)の変更リストです。より詳細な議論やレビューコメントが含まれている可能性があります。

参考にした情報源リンク

  • Go言語の公式ドキュメント: sync/atomicパッケージ
  • ARMアーキテクチャリファレンスマニュアル (特にARMv5に関するセクション)
  • Go言語のソースコード(src/pkg/sync/atomicディレクトリ)
  • Go言語のIssueトラッカー (Issue #3331)
  • Go言語のGerritコードレビューシステム (CL 6210071)
  • Compare-and-Swap (CAS) に関する一般的な並行プログラミングの資料
  • Go言語のARMポートに関する議論やドキュメント

[インデックス 13131] ファイルの概要

このコミットは、Go言語のsync/atomicパッケージにおける64ビットアトミック操作(LoadStoreAdd)のLinux/ARMアーキテクチャ向け実装に関するものです。特に、ARMv5アーキテクチャ上でGOARM=5が設定された環境で、これらの操作が正しく機能するように改善することを目的としています。変更の核心は、CompareAndSwapUint64(64ビットの比較と交換)操作を基盤として、他の64ビットアトミック操作をソフトウェア的に実装することにあります。

コミット

commit 090f9fc3ef532597c33f0702dfd541ca95529efd
Author: Shenghou Ma <minux.ma@gmail.com>
Date:   Wed May 23 02:02:01 2012 +0800

    sync/atomic: use cas64 to implement {Load,Store,Add}{Uint,Int}64 on Linux/ARM
            Now with GOARM=5 our all.bash should pass on ARMv5 systems.
            Fixes #3331.
    
    R=golang-dev, rsc, dvyukov
    CC=golang-dev
    https://golang.org/cl/6210071

GitHub上でのコミットページへのリンク

https://github.com/golang/go/commit/090f9fc3ef532597c33f0702dfd541ca95529efd

元コミット内容

sync/atomic: use cas64 to implement {Load,Store,Add}{Uint,Int}64 on Linux/ARM
        Now with GOARM=5 our all.bash should pass on ARMv5 systems.
        Fixes #3331.

変更の背景

このコミットの主な背景は、Go言語がARMv5アーキテクチャ上で64ビットのアトミック操作を正しくサポートしていなかった問題にあります。Goのテストスイートであるall.bashが、GOARM=5(ARMv5アーキテクチャをターゲットとする設定)の環境で失敗するというバグ(Issue #3331)が報告されていました。

ARMv5のような古いARMアーキテクチャでは、64ビットの値を単一の命令でアトミックに操作するための専用ハードウェア命令が不足しているか、効率的ではありませんでした。特に、CompareAndSwap(CAS)のような複合的なアトミック操作は、複数の命令に分解される可能性があり、その間に他のプロセッサやコアがメモリにアクセスすることで競合状態が発生し、操作の原子性が保証されない問題がありました。

この問題は、並行処理において共有メモリ上のデータを安全に操作するために不可欠なアトミック操作の信頼性を損なうものであり、GoプログラムがARMv5環境で正しく動作しない原因となっていました。このコミットは、この根本的な問題を解決し、ARMv5上でのGoの安定性と互換性を向上させることを目的としています。

前提知識の解説

1. アトミック操作 (Atomic Operations)

アトミック操作とは、複数のCPUコアやスレッドから同時にアクセスされた場合でも、その操作全体が不可分(分割不可能)であることを保証する操作です。つまり、操作の途中で他の操作が割り込むことがなく、常に一貫した状態を保ちます。これは、並行プログラミングにおいて共有データの一貫性を保ち、競合状態(Race Condition)を防ぐために非常に重要です。

一般的なアトミック操作には以下のようなものがあります。

  • アトミックロード (Atomic Load): メモリから値を読み込む操作がアトミックに行われることを保証します。
  • アトミックストア (Atomic Store): メモリに値を書き込む操作がアトミックに行われることを保証します。
  • 比較と交換 (Compare-and-Swap, CAS): 特定のメモリ位置の値が期待する値と一致する場合にのみ、その値を新しい値に更新する操作です。この操作全体がアトミックに行われます。CASは、ロックフリーなデータ構造やアルゴリズムを実装するための基本的なプリミティブとして広く使用されます。
  • アトミック加算 (Atomic Add): メモリ上の値に指定された値をアトミックに加算します。

2. ARMアーキテクチャと64ビット操作

ARM(Advanced RISC Machine)は、モバイルデバイスや組み込みシステムで広く使用されているCPUアーキテクチャです。ARMアーキテクチャには多くのバージョンがあり、それぞれ異なる命令セットや機能を持っています。

  • ARMv5: 比較的に古いARMアーキテクチャのバージョンです。このバージョンでは、64ビットの整数値を直接扱うための効率的なハードウェアサポートが限られている場合があります。特に、64ビットのCASのような複合的なアトミック操作は、単一のCPU命令として提供されていないことが多く、複数の32ビット命令の組み合わせでエミュレートする必要がありました。このエミュレーションは、原子性を保証するために特別な注意(例えば、カーネルレベルのヘルパー関数や、スピンロックのようなソフトウェア的な同期メカニズム)を必要とします。

3. Go言語のsync/atomicパッケージ

Go言語のsync/atomicパッケージは、低レベルのアトミック操作を提供します。これにより、ミューテックス(sync.Mutex)のようなロックメカニズムを使用せずに、共有変数を安全に操作できます。ロックを使用しないため、特定のシナリオではより高いパフォーマンスを実現できますが、正しく使用しないとデッドロックや競合状態を引き起こす可能性があります。

sync/atomicパッケージは、CPUが提供するアトミック命令を最大限に活用するように設計されています。しかし、特定のアーキテクチャ(この場合はARMv5)で必要なアトミック命令が直接利用できない場合、Goランタイムはソフトウェア的なフォールバックや、より基本的なアトミックプリミティブ(例えば、32ビットCAS)を組み合わせて64ビット操作をエミュレートする必要があります。

4. GOARM環境変数

GOARMはGoコンパイラがARMアーキテクチャ向けにコードを生成する際に使用する環境変数です。この変数は、ターゲットとするARMプロセッサのバージョン(例: ARMv5、ARMv6、ARMv7)を指定します。GOARM=5は、GoコンパイラがARMv5アーキテクチャに最適化されたコードを生成するように指示します。ARMv5は浮動小数点演算ユニット(FPU)を持たないことが多いため、この設定は特に重要です。

5. all.bash

all.bashはGo言語のソースツリーに含まれるスクリプトで、Goのビルド、テスト、およびインストールプロセス全体を実行します。これは、Goの変更が様々なプラットフォームやアーキテクチャで正しく機能するかどうかを確認するための主要なテストスイートです。all.bashが特定の環境で失敗するということは、その環境でのGoの動作に問題があることを示します。

技術的詳細

このコミットの技術的詳細は、ARMv5アーキテクチャにおける64ビットアトミック操作の課題と、それをGo言語のsync/atomicパッケージでどのように解決したかに集約されます。

ARMv5は32ビットアーキテクチャであり、64ビットの値をレジスタに一度にロードしたり、単一の命令で操作したりすることができません。そのため、64ビットの値を扱う際には、2つの32ビットレジスタに分割して処理する必要があります。この分割処理が、アトミック操作の実現を困難にします。

従来のARMv5では、64ビットのアトミック操作(特にCAS)をハードウェアで直接サポートする命令がありませんでした。そのため、Goのランタイムは、これらの操作をソフトウェア的にエミュレートする必要がありました。しかし、単純なソフトウェアエミュレーションでは、複数の命令に分割された操作の途中でコンテキストスイッチや割り込みが発生し、原子性が破られる可能性がありました。

このコミットでは、以下の戦略を採用しています。

  1. CompareAndSwapUint64を基盤とする: ARMv5上で64ビットのCompareAndSwapcas64)を正しく実装することが、他の64ビットアトミック操作を実現するための鍵となります。このcas64は、おそらくカーネルのヘルパー関数や、より低レベルなアセンブリ言語のプリミティブ(例えば、LDREX/STREXのような排他ロード/ストア命令の組み合わせ、または古いARMではスピンロックを用いたソフトウェア的な実装)を利用して、原子性を保証するように実装されています。コミットメッセージのuse cas64という記述から、このcas64がARMv5上で64ビットのCASをアトミックに実行できる何らかのメカニズムを指していることがわかります。

  2. 高レベルなアトミック操作のソフトウェア実装: LoadUint64StoreUint64AddUint64といった高レベルな64ビットアトミック操作は、新しく追加されたsrc/pkg/sync/atomic/64bit_linux_arm.goファイル内でGo言語によって実装されています。これらの実装は、CompareAndSwapUint64を繰り返し呼び出すループ(スピンロックに似たメカニズム)を使用しています。

    • loadUint64(addr *uint64): この関数は、addrが指す64ビットの値をアトミックに読み込みます。実装は、無限ループ内で*addrの現在の値を読み込み、その値を使ってCompareAndSwapUint64(addr, val, val)を実行します。CompareAndSwapUint64は、addrの値がvalと一致する場合にのみvalvalに書き換える(つまり、何も変更しない)操作ですが、この操作が成功したということは、読み込んだvalがその時点での最新の値であり、他のゴルーチンによって変更されていないことを意味します。これにより、読み込み操作の原子性が保証されます。

    • storeUint64(addr *uint64, val uint64): この関数は、addrが指す64ビットのメモリ位置にvalをアトミックに書き込みます。実装は、無限ループ内で*addrの現在の値(old)を読み込み、CompareAndSwapUint64(addr, old, val)を実行します。このCAS操作が成功した場合、oldの値がvalにアトミックに更新されたことになり、ループを抜けます。失敗した場合は、他のゴルーチンがaddrの値を変更したことを意味するため、ループを続行して再試行します。

    • addUint64(val *uint64, delta uint64): この関数は、valが指す64ビットの値にdeltaをアトミックに加算します。実装は、無限ループ内で*valの現在の値(old)を読み込み、old + deltaを計算して新しい値(`new