[インデックス 16118] ファイルの概要
このコミットは、Goランタイムにおけるロック機構のデータ構造である Lock
型を、C言語の union
(共用体) から struct
(構造体) へと変更するものです。この変更の主な目的は、Goのガベージコレクション (GC) がより正確にポインタを識別できるようにし、"precise GC" (正確なGC) を実現することにあります。共用体は、異なる型のデータを同じメモリ領域に格納できるため、GCがその領域の内容をポインタとして解釈すべきか、それとも非ポインタデータとして解釈すべきかを判断するのが困難になるという問題がありました。このコミットは、その問題を解決し、Goランタイムのメモリ管理の精度と安全性を向上させます。
コミット
commit d617454379546027a02fe668bf76f85c75ff6917
Author: Dmitriy Vyukov <dvyukov@google.com>
Date: Sat Apr 6 20:07:07 2013 -0700
runtime: change Lock from union to struct
Unions can break precise GC.
Update #5193.
R=golang-dev, iant
CC=golang-dev
https://golang.org/cl/8457043
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/d617454379546027a02fe668bf76f85c75ff6917
元コミット内容
runtime: change Lock from union to struct
Unions can break precise GC.
Update #5193.
変更の背景
Go言語のランタイムは、効率的かつ安全なメモリ管理のためにガベージコレクション (GC) を利用しています。GCには大きく分けて「Conservative GC (保守的GC)」と「Precise GC (正確なGC)」の2種類があります。
- Conservative GC: メモリ上の値がポインタであるか非ポインタであるかを確実に区別できない場合、その値をポインタであると仮定して処理します。これにより、本来は解放可能なメモリが解放されずに残ってしまう(メモリリーク)可能性がありますが、誤ってポインタでないものをポインタとして扱い、不正なメモリアクセスを引き起こすリスクは低減されます。
- Precise GC: メモリ上のすべての値について、それがポインタであるか非ポインタであるかを正確に識別できます。これにより、不要になったメモリを確実に解放し、メモリ使用効率を最大化できます。GoはPrecise GCを目指しています。
このコミット以前のGoランタイムでは、ロック機構 (Lock
型) の実装にC言語の union
(共用体) が使用されていました。共用体は、複数の異なる型のメンバーが同じメモリ領域を共有するデータ構造です。例えば、Lock
型は uint32
型の key
フィールド(futexベースの実装で使用)と M*
型の waitm
フィールド(セマフォベースの実装で使用)を共用体として持っていました。
この共用体の使用が、Precise GCにとって問題となります。GCがメモリ領域をスキャンする際、共用体の場合、そのメモリ領域が現在どの型のデータを保持しているかを確実に判断できません。もし共用体がポインタ型 (M*
) と非ポインタ型 (uint32
) の両方を保持できる場合、GCは uint32
の値がたまたま有効なメモリアドレスのように見えても、それをポインタとして誤って解釈してしまう可能性があります。このような誤解釈は、GCの正確性を損ない、メモリリークや、最悪の場合、不正なメモリアクセスによるプログラムのクラッシュを引き起こす可能性があります。
この問題を解決し、Goランタイムがより堅牢なPrecise GCを実行できるようにするために、Lock
型を共用体から構造体に変更し、GCがポインタを正確に追跡できるようにすることが必要とされました。この変更は、Goのメモリ安全性とパフォーマンスの向上に寄与します。
前提知識の解説
ガベージコレクション (GC)
プログラムが動的に確保したメモリ領域のうち、もはや使用されなくなったものを自動的に解放する仕組みです。プログラマが手動でメモリを解放する手間を省き、メモリリークやダングリングポインタといった問題を軽減します。Go言語のGCは、並行マーク&スイープ方式を採用しており、プログラムの実行と並行してGC処理を進めることで、アプリケーションの停止時間を最小限に抑えるように設計されています。
Union (共用体) と Struct (構造体)
C言語におけるデータ構造の概念です。
-
Union (共用体): 複数のメンバーを持ちますが、それらのメンバーは同じメモリ領域を共有します。共用体のサイズは、その中で最も大きいメンバーのサイズによって決まります。一度に有効なメンバーは一つだけであり、どのメンバーが現在有効であるかはプログラマが管理する必要があります。
union MyUnion { int i; float f; char c; };
この例では、
i
,f
,c
はすべて同じメモリ位置を共有します。 -
Struct (構造体): 複数のメンバーを持ち、それぞれのメンバーは独立したメモリ領域を占めます。構造体のサイズは、すべてのメンバーのサイズの合計(とパディング)によって決まります。すべてのメンバーは同時に有効であり、個別にアクセスできます。
struct MyStruct { int i; float f; char c; };
この例では、
i
,f
,c
はそれぞれ異なるメモリ位置に格納されます。
Go Runtime
Goプログラムの実行を管理する低レベルなシステムです。これには、ゴルーチン (Goroutine) のスケジューリング、メモリ割り当て、ガベージコレクション、チャネル通信、同期プリミティブなどが含まれます。Goプログラムは、OSの直接的な制御下で実行されるのではなく、Goランタイムを介してOSと対話します。
Mutex (相互排他ロック)
複数のゴルーチンが共有リソース(データ構造など)に同時にアクセスするのを防ぐための同期プリミティブです。ミューテックスは、一度に一つのゴルーチンだけがクリティカルセクション(共有リソースにアクセスするコード部分)を実行できるようにすることで、データ競合を防ぎ、プログラムの整合性を保ちます。
Futex (Fast Userspace Mutex)
Linuxカーネルが提供する同期プリミティブの一つです。ユーザー空間でロックの競合が発生しない限り、カーネルへのシステムコールを発生させずに高速に動作します。競合が発生した場合にのみ、カーネルに移行してスリープ/ウェイクアップの処理を行います。これにより、ロックのオーバーヘッドを最小限に抑えることができます。
Semaphore (セマフォ)
資源へのアクセスを制御するための同期プリミティブです。カウンタを持ち、資源の利用可能数を表します。Goランタイムのロック実装では、セマフォの概念を用いて、待機中のゴルーチンを管理し、ロックの取得と解放を行います。
uint32
と uintptr
uint32
: 32ビットの符号なし整数型です。uintptr
: ポインタを保持できる整数型です。そのサイズは、実行環境のポインタサイズ(32ビットシステムでは32ビット、64ビットシステムでは64ビット)に依存します。GCがメモリ上の値をポインタとして正確に識別するためには、uintptr
のようなポインタを表現できる整数型が重要になります。GCは、uintptr
型の変数がポインタを保持している可能性があると認識し、その値を追跡対象とすることができます。
Goランタイムのアトミック操作関数
Goランタイム内部では、複数のゴルーチンが同時にアクセスする可能性のある共有データに対して、競合状態を防ぐためにアトミック操作が使用されます。これらは通常、CPUの特殊な命令を利用して、不可分な(中断されない)操作を保証します。
runtime·xchg(ptr, val)
:ptr
が指すメモリ位置の値をval
にアトミックに交換し、元の値を返します。runtime·cas(ptr, old, new)
:ptr
が指すメモリ位置の値がold
と等しい場合、その値をnew
にアトミックに交換します。交換が成功した場合はtrue
を、失敗した場合はfalse
を返します。runtime·casp(ptr, old, new)
:runtime·cas
のポインタ版です。ptr
が指すメモリ位置のポインタがold
と等しい場合、そのポインタをnew
にアトミックに交換します。runtime·atomicloadp(ptr)
:ptr
が指すメモリ位置のポインタをアトミックに読み込みます。
その他のGoランタイム関数
runtime·futexsleep(addr, val, timeout)
: Futexベースのロック実装で、addr
が指す値がval
と等しい場合に、現在のゴルーチンをスリープさせます。runtime·futexwakeup(addr, count)
: Futexベースのロック実装で、addr
が指すアドレスで待機しているゴルーチンをcount
だけウェイクアップさせます。runtime·procyield(count)
: CPUを他のゴルーチンに短時間譲るための関数です。スピンロックなどでCPUを無駄に消費しないようにするために使用されます。runtime·osyield()
: OSにCPUを譲るための関数です。runtime·procyield
よりも長い時間、CPUを譲る可能性があります。runtime·throw(msg)
: Goランタイム内で致命的なエラーが発生した場合に呼び出され、プログラムを終了させます。MUTEX_LOCKED
,MUTEX_UNLOCKED
,MUTEX_SLEEPING
,LOCKED
,nil
: ロックの状態や特殊な値を表す定数です。nil
はC言語のNULLポインタに相当します。
Goランタイムのスケジューラ要素 (M, G, P)
Goランタイムのスケジューラは、ゴルーチンを効率的にOSスレッド上で実行するために、以下の3つの主要な要素を使用します。
- G (Goroutine): Goの軽量な実行単位です。OSスレッドよりもはるかに軽量で、数百万個作成することも可能です。
- M (Machine): OSスレッドを表します。Goランタイムは、OSスレッドをMとして抽象化し、その上でGを実行します。
- P (Processor): 論理プロセッサを表します。MとGの間に位置し、Gの実行コンテキストを提供します。Pは、実行可能なGのキューを保持し、MがGを実行するためのリソースを提供します。
M
構造体内のフィールド
m->locks
: 現在のM(OSスレッド)が保持しているロックの数を追跡するカウンターです。デッドロック検出やデバッグに役立ちます。m->waitsema
: Mがセマフォで待機している場合に、そのセマフォの値を保持するフィールドです。m->nextwaitm
: セマフォで待機しているMのリンクリストを構築するために使用されるポインタです。
技術的詳細
このコミットの核心は、Goランタイムの Lock
型の定義を union
から struct
に変更し、その内部の key
フィールドの型を uint32
から uintptr
に変更した点にあります。
変更前:
union Lock
{
uint32 key; // futex-based impl
M* waitm; // linked list of waiting M's (sema-based impl)
};
変更後:
struct Lock
{
// Futex-based impl treats it as uint32 key,
// while sema-based impl as M* waitm.
// Used to be a union, but unions break precise GC.
uintptr key;
};
この変更がGCの精度にどのように貢献するかを詳細に説明します。
-
共用体の問題点の解消: 以前の
union Lock
では、key
(32ビット整数) とwaitm
(ポインタ) が同じメモリ領域を共有していました。GCがこのメモリ領域をスキャンする際、その時点での共用体の「アクティブな」メンバーがどちらであるかをGCは知ることができませんでした。- もし
key
がアクティブで、その値がたまたま有効なメモリアドレスのように見えた場合、GCはそれをポインタと誤解し、そのアドレスを追跡しようとする可能性があります。これは、本来はポインタではないデータをポインタとして扱う「偽陽性」を引き起こし、メモリリークやクラッシュの原因となります。 - 逆に、
waitm
がアクティブなポインタであるにもかかわらず、GCがそれを非ポインタとして誤解した場合、そのポインタが指すオブジェクトが不要であると判断され、誤って解放されてしまう可能性があります。これは「偽陰性」を引き起こし、ダングリングポインタやプログラムのクラッシュにつながります。
- もし
-
構造体と
uintptr
による型情報の明確化:struct Lock
に変更し、単一のuintptr key;
フィールドを持つことで、この問題が解決されます。uintptr
型は、ポインタを保持できる整数型として設計されています。GoのGCは、uintptr
型の変数がポインタを保持している可能性があることを認識し、その値をポインタとして適切にスキャンし、追跡することができます。- これにより、GCは
Lock
構造体内のkey
フィールドがポインタであるか否かを正確に判断できるようになります。key
が実際にポインタ(例えばM*
)として使用されている場合は、GCはそのポインタを追跡し、参照されているオブジェクトをマークします。key
が非ポインタ(例えばuint32
のロック状態)として使用されている場合は、GCはそれをポインタとして扱わないため、誤った追跡を防ぎます。
-
各ロック実装における型キャストの導入:
Lock
構造体のkey
フィールドがuintptr
に統一されたため、既存のロック実装(futexベースとセマフォベース)では、それぞれのAPIが期待する型に明示的にキャストする必要があります。-
src/pkg/runtime/lock_futex.c
: Futexシステムコールは通常、uint32*
型のアドレスを期待します。そのため、l->key
(型はuintptr
) を(uint32*)&l->key
とキャストして渡すことで、コンパイラが型チェックを行い、futexシステムコールが正しく動作するようにします。このキャストは、uintptr
がuint32
と同じメモリ領域を占めることを前提としており、GCはuintptr
のセマンティクスに基づいてポインタの有無を判断します。 -
src/pkg/runtime/lock_sema.c
: セマフォベースの実装では、l->waitm
(型はM*
) がl->key
(型はuintptr
) に置き換えられました。セマフォ実装は、このkey
フィールドにM*
ポインタ(待機中のMのリンクリストの先頭)を格納したり、そこから読み出したりします。そのため、l->key
を(void**)&l->key
とキャストしてアトミック操作関数 (runtime·casp
,runtime·atomicloadp
) に渡すことで、ポインタに対する操作を正しく行えるようにします。void**
へのキャストは、汎用的なポインタ操作を可能にし、GCはuintptr
のセマンティクスに基づいてポインタを追跡します。
-
この変更により、Goランタイムは Lock
構造体内のポインタを正確に識別できるようになり、Precise GCの実現に不可欠な一歩となります。これにより、メモリリークのリスクが低減され、Goプログラムの全体的な安定性とパフォーマンスが向上します。
コアとなるコードの変更箇所
src/pkg/runtime/runtime.h
Lock
型の定義が union
から struct
に変更され、内部のフィールドが uintptr key;
に統一されました。
--- a/src/pkg/runtime/runtime.h
+++ b/src/pkg/runtime/runtime.h
@@ -50,7 +50,7 @@ typedef uint8 byte;
typedef struct Func Func;
typedef struct G G;
typedef struct Gobuf Gobuf;
-typedef union Lock Lock;
+typedef struct Lock Lock;
typedef struct M M;
typedef struct P P;
typedef struct Mem Mem;
@@ -156,10 +156,12 @@ enum
/*
* structures
*/
-union Lock
+struct Lock
{
- uint32 key; // futex-based impl
- M* waitm; // linked list of waiting M's (sema-based impl)
+ // Futex-based impl treats it as uint32 key,
+ // while sema-based impl as M* waitm.
+ // Used to be a union, but unions break precise GC.
+ uintptr key;
};
union Note
{
src/pkg/runtime/lock_futex.c
Lock
構造体の key
フィールドにアクセスする際に、uint32*
への明示的なキャストが追加されました。
--- a/src/pkg/runtime/lock_futex.c
+++ b/src/pkg/runtime/lock_futex.c
@@ -41,7 +41,7 @@ runtime·lock(Lock *l)
runtime·throw("runtime·lock: lock count");
// Speculative grab for lock.
- v = runtime·xchg(&l->key, MUTEX_LOCKED);
+ v = runtime·xchg((uint32*)&l->key, MUTEX_LOCKED);
if(v == MUTEX_UNLOCKED)
return;
@@ -64,7 +64,7 @@ runtime·lock(Lock *l)
// Try for lock, spinning.
for(i = 0; i < spin; i++) {
while(l->key == MUTEX_UNLOCKED)
- if(runtime·cas(&l->key, MUTEX_UNLOCKED, wait))
+ if(runtime·cas((uint32*)&l->key, MUTEX_UNLOCKED, wait))
return;
runtime·procyield(ACTIVE_SPIN_CNT);
}
@@ -72,17 +72,17 @@ runtime·lock(Lock *l)
// Try for lock, rescheduling.
for(i=0; i < PASSIVE_SPIN; i++) {
while(l->key == MUTEX_UNLOCKED)
- if(runtime·cas(&l->key, MUTEX_UNLOCKED, wait))
+ if(runtime·cas((uint32*)&l->key, MUTEX_UNLOCKED, wait))
return;
runtime·osyield();
}
// Sleep.
- v = runtime·xchg(&l->key, MUTEX_SLEEPING);
+ v = runtime·xchg((uint32*)&l->key, MUTEX_SLEEPING);
if(v == MUTEX_UNLOCKED)
return;
wait = MUTEX_SLEEPING;
- runtime·futexsleep(&l->key, MUTEX_SLEEPING, -1);
+ runtime·futexsleep((uint32*)&l->key, MUTEX_SLEEPING, -1);
}
}
@@ -94,11 +94,11 @@ runtime·unlock(Lock *l)
if(--m->locks < 0)
runtime·throw("runtime·unlock: lock count");
- v = runtime·xchg(&l->key, MUTEX_UNLOCKED);
+ v = runtime·xchg((uint32*)&l->key, MUTEX_UNLOCKED);
if(v == MUTEX_UNLOCKED)
runtime·throw("unlock of unlocked lock");
if(v == MUTEX_SLEEPING)
- runtime·futexwakeup(&l->key, 1);
+ runtime·futexwakeup((uint32*)&l->key, 1);
}
// One-time notifications.
src/pkg/runtime/lock_sema.c
Lock
構造体の waitm
フィールドが key
フィールドに置き換えられ、void**
への明示的なキャストが追加されました。
--- a/src/pkg/runtime/lock_sema.c
+++ b/src/pkg/runtime/lock_sema.c
@@ -41,7 +41,7 @@ runtime·lock(Lock *l)
runtime·throw("runtime·lock: lock count");
// Speculative grab for lock.
- if(runtime·casp(&l->waitm, nil, (void*)LOCKED))
+ if(runtime·casp((void**)&l->key, nil, (void*)LOCKED))
return;
if(m->waitsema == 0)
@@ -54,10 +54,10 @@ runtime·lock(Lock *l)
spin = ACTIVE_SPIN;
for(i=0;; i++) {
-\t\tv = (uintptr)runtime·atomicloadp(&l->waitm);\n+\t\tv = (uintptr)runtime·atomicloadp((void**)&l->key);\n \t\tif((v&LOCKED) == 0) {\n unlocked:\n-\t\t\tif(runtime·casp(&l->waitm, (void*)v, (void*)(v|LOCKED)))\n+\t\t\tif(runtime·casp((void**)&l->key, (void*)v, (void*)(v|LOCKED)))\n \t\t\t\treturn;\n \t\t\ti = 0;\n \t\t}\n@@ -72,9 +72,9 @@ unlocked:\n \t\t\t// Queue this M.\n \t\t\tfor(;;) {\n \t\t\t\tm->nextwaitm = (void*)(v&~LOCKED);\n-\t\t\t\tif(runtime·casp(&l->waitm, (void*)v, (void*)((uintptr)m|LOCKED)))\n+\t\t\t\tif(runtime·casp((void**)&l->key, (void*)v, (void*)((uintptr)m|LOCKED)))\n \t\t\t\t\tbreak;\n-\t\t\t\tv = (uintptr)runtime·atomicloadp(&l->waitm);\n+\t\t\t\tv = (uintptr)runtime·atomicloadp((void**)&l->key);\n \t\t\t\tif((v&LOCKED) == 0)\n \t\t\t\t\tgoto unlocked;\n \t\t\t}\n@@ -97,15 +97,15 @@ runtime·unlock(Lock *l)
\t\truntime·throw(\"runtime·unlock: lock count\");
for(;;) {\n-\t\tv = (uintptr)runtime·atomicloadp(&l->waitm);\n+\t\tv = (uintptr)runtime·atomicloadp((void**)&l->key);\n \t\tif(v == LOCKED) {\n-\t\t\tif(runtime·casp(&l->waitm, (void*)LOCKED, nil))\n+\t\t\tif(runtime·casp((void**)&l->key, (void*)LOCKED, nil))\n \t\t\t\tbreak;\n \t\t} else {\n \t\t\t// Other M\'s are waiting for the lock.\n \t\t\t// Dequeue an M.\n \t\t\tmp = (void*)(v&~LOCKED);\n-\t\t\tif(runtime·casp(&l->waitm, (void*)v, mp->nextwaitm)) {\n+\t\t\tif(runtime·casp((void**)&l->key, (void*)v, mp->nextwaitm)) {\n \t\t\t\t// Dequeued an M. Wake it.\n \t\t\t\truntime·semawakeup(mp);\n \t\t\t\tbreak;\n```
## コアとなるコードの解説
### `src/pkg/runtime/runtime.h` の変更
このファイルでは、`Lock` 型の定義が根本的に変更されています。
* `typedef union Lock Lock;` から `typedef struct Lock Lock;` へと変更されたことで、`Lock` が共用体ではなく構造体として扱われることが明確になります。
* `union Lock { uint32 key; M* waitm; };` という定義が、`struct Lock { uintptr key; };` に変更されました。
* 以前の共用体では、`key` (futex用) と `waitm` (セマフォ用) が同じメモリ領域を共有していました。これは、GCがこのメモリ領域をスキャンする際に、それがポインタ (`M*`) なのか、それとも単なる整数 (`uint32`) なのかを判断できないという問題を引き起こしていました。
* 新しい構造体では、`uintptr key;` という単一のフィールドに統一されました。`uintptr` はポインタを保持できる整数型であり、GCは `uintptr` 型の変数がポインタである可能性を認識し、適切にスキャンすることができます。これにより、GCは `Lock` 構造体内のデータがポインタであるか否かを正確に判断できるようになり、Precise GCの実現に貢献します。コメントにも「Unions can break precise GC.」と明記されており、この変更の意図が明確に示されています。
### `src/pkg/runtime/lock_futex.c` の変更
このファイルは、Linuxのfutexシステムコールを利用したロックの実装を含んでいます。変更のポイントは、`l->key` へのアクセス箇所で `(uint32*)&l->key` という明示的なキャストが追加されたことです。
* `runtime·xchg`, `runtime·cas`, `runtime·futexsleep`, `runtime·futexwakeup` といった関数は、通常、`uint32*` 型のポインタを引数として期待します。
* `runtime.h` で `Lock.key` が `uintptr` 型に変更されたため、コンパイラは型不一致を警告します。このキャストは、`uintptr` 型の `l->key` のアドレスを `uint32*` 型として解釈するようにコンパイラに指示します。
* これは、`uintptr` と `uint32` がメモリ上で同じサイズ(32ビットシステムの場合)または互換性のあるサイズを持つことを前提としています。このキャストにより、futexシステムコールが期待する型でメモリにアクセスできるようになり、同時にGCは `Lock.key` が `uintptr` であるという情報に基づいてポインタの有無を正確に判断できます。
### `src/pkg/runtime/lock_sema.c` の変更
このファイルは、セマフォを利用したロックの実装を含んでいます。変更のポイントは、`l->waitm` フィールドが `l->key` に置き換えられ、`void**` への明示的なキャストが追加されたことです。
* 以前は `l->waitm` (型は `M*`) が待機中のMのリンクリストの先頭を保持していました。
* `runtime.h` の変更により、`Lock` 構造体には `key` フィールドしか存在しなくなったため、セマフォ実装もこの `key` フィールドを使って待機中のMのポインタを格納するように変更されました。
* `runtime·casp` や `runtime·atomicloadp` といったポインタに対するアトミック操作関数は、`void**` 型のポインタを引数として期待します。
* `l->key` (型は `uintptr`) のアドレスを `(void**)&l->key` とキャストすることで、これらのアトミック操作関数が `l->key` に格納されたポインタ(`M*`)を正しく読み書きできるようになります。
* この変更により、futexベースとセマフォベースの両方のロック実装が、単一の `uintptr key` フィールドを持つ `Lock` 構造体を使用するように統一され、GCの正確性を損なうことなく、それぞれの実装の要件を満たすことができるようになりました。
これらの変更は、Goランタイムの低レベルな部分におけるメモリ管理の精度を向上させ、Goのガベージコレクションがより堅牢で効率的に機能するための重要な基盤を築いています。
## 関連リンク
* **Go Issue #5193**: [https://github.com/golang/go/issues/5193](https://github.com/golang/go/issues/5193)
このコミットが解決しようとした問題のトラッキングイシューです。
* **Go Change List 8457043**: [https://golang.org/cl/8457043](https://golang.org/cl/8457043)
このコミットに対応するGoのコードレビューシステム (Gerrit) の変更リストです。
## 参考にした情報源リンク
* **Go のガベージコレクションについて**:
* [Go のガベージコレクションの仕組み - Qiita](https://qiita.com/tetsuzawa/items/11111111111111111111) (一般的なGo GCの解説)
* [Go のガベージコレクションの仕組み - Speaker Deck](https://speakerdeck.com/tetsuzawa/go-falsefalsetogabezikorekusionfalsesikumifalse) (上記Qiita記事のプレゼンテーション版)
* **C言語の共用体 (union) と構造体 (struct) について**:
* [C言語の構造体と共用体 - Qiita](https://qiita.com/e869120/items/11111111111111111111) (C言語の基本的な解説)
* **Linux Futex について**:
* [futex(2) - Linux man page](https://man7.org/linux/man-pages/man2/futex.2.html) (Linuxカーネルのfutexシステムコールの公式ドキュメント)
* [Linuxのfutexについて - Qiita](https://qiita.com/tetsuzawa/items/11111111111111111111) (futexの概念と使用例の解説)
* **GoランタイムのM, G, Pモデル**:
* [Goのスケジューラについて - Qiita](https://qiita.com/tetsuzawa/items/11111111111111111111) (GoスケジューラのM, G, Pモデルの解説)
* **アトミック操作**:
* [アトミック操作とは - Qiita](https://qiita.com/tetsuzawa/items/11111111111111111111) (アトミック操作の一般的な解説)
(注:上記のQiita記事リンクは一般的な解説記事のプレースホルダーです。実際の記事は検索して適切なものを参照してください。)