[インデックス 16312] ファイルの概要
このコミットは、Goランタイムにおけるロック解除処理の順序を修正し、プリエンプティブスケジューラとの連携を改善することを目的としています。具体的には、m->locks
カウンタのデクリメントを、実際のロック解除操作が完了した後に行うように変更しています。これにより、ロックが完全に解除される前にゴルーチンがプリエンプトされることを防ぎ、スケジューラの正確性とシステムの安定性を向上させます。
コミット
commit 764bb36ea2be3fd9c04a0a485524a75241c21b8a
Author: Dmitriy Vyukov <dvyukov@google.com>
Date: Wed May 15 16:48:41 2013 +0400
runtime: unset m->locks after actual lock unlock
This is needed for preemptive scheduler,
it will preempt only when m->locks==0,
and we do not want to be preempted while
we have not completely unlocked the lock.
R=golang-dev, khr, iant
CC=golang-dev
https://golang.org/cl/9196047
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/764bb36ea2be3fd9c04a0a485524a75241c21b8a
元コミット内容
Goランタイムにおいて、ロック解除処理の際に m->locks
カウンタをデクリメントするタイミングを、実際のロック解除操作が完了した後に行うように変更しました。これは、プリエンプティブスケジューラが m->locks
が0の場合にのみプリエンプションを行うため、ロックが完全に解除される前にプリエンプションが発生することを避けるために必要です。
変更の背景
Goランタイムは、複数のゴルーチン(軽量スレッド)を効率的に実行するために、独自のスケジューラを持っています。このスケジューラは、ゴルーチンが長時間CPUを占有するのを防ぐために、プリエンプション(横取り)のメカニズムを備えています。プリエンプティブスケジューラは、特定の条件が満たされた場合に、実行中のゴルーチンを一時停止し、別のゴルーチンにCPUを割り当てます。
このコミットの背景にある問題は、プリエンプティブスケジューラがゴルーチンをプリエンプトする条件の一つとして、m->locks
というカウンタが0であることを見ていた点にあります。m->locks
は、現在のM(OSスレッド)が保持しているランタイムロックの数を追跡するカウンタです。ロックが保持されている間は、ランタイムの重要なデータ構造が保護されており、その間にプリエンプションが発生すると、データ競合やデッドロックなどの深刻な問題を引き起こす可能性があります。
以前の実装では、ロック解除処理の冒頭で m->locks
をデクリメントしていました。しかし、実際のロック解除操作(例えば、フューテックスやセマフォの解放)は、その後に実行されていました。このわずかな時間差の間に、プリエンプティブスケジューラが m->locks
が0になったと判断し、ロックがまだ完全に解除されていないゴルーチンをプリエンプトしてしまう可能性がありました。このような状況は、ランタイムの内部状態を不安定にし、予測不能なバグにつながる恐れがありました。
このコミットは、このタイミングの問題を解決し、ランタイムの堅牢性を高めることを目的としています。
前提知識の解説
このコミットを理解するためには、以下のGoランタイムの概念と同期プリミティブに関する知識が必要です。
Goランタイムスケジューラ (M, P, G)
Goランタイムスケジューラは、G(ゴルーチン)、M(OSスレッド)、P(プロセッサ)という3つの主要な要素で構成されています。
- G (Goroutine): Goにおける軽量な実行単位です。数KBのスタックを持ち、数百万個のゴルーチンを同時に実行できます。
- M (Machine/OS Thread): 実際のOSスレッドです。Gを実行する物理的なスレッドであり、OSのスケジューラによって管理されます。
- P (Processor): MとGを仲介する論理的なプロセッサです。Pは実行可能なGのキューを保持し、MにGを割り当てます。Pの数は通常、CPUコアの数に制限され、MがGを実行するためにPを必要とします。
スケジューラは、GをPに割り当て、PがMにGを実行させます。これにより、OSスレッドの数を制限しつつ、多数のゴルーチンを効率的に多重化して実行できます。
プリエンプティブスケジューラ
プリエンプティブスケジューラは、実行中のタスク(この場合はゴルーチン)が自発的にCPUを解放するのを待つのではなく、一定の条件や時間に基づいて強制的にCPUの利用を中断させ、別のタスクに切り替える方式です。Goランタイムでは、ゴルーチンが長時間実行され続けることによるスケジューリングの遅延を防ぐために、プリエンプションが導入されています。
Goのプリエンプションは、主に以下の2つのメカニズムで行われます。
- 協調的プリエンプション: 関数呼び出しやチャネル操作など、特定の安全なポイントでゴルーチンが自発的にスケジューラに制御を戻すことで発生します。
- 非同期プリエンプション (Async Preemption): Go 1.14以降で導入された、より強力なプリエンプションメカニズムです。これは、実行中のゴルーチンがシステムコールやGCなどの特定のランタイム操作中に長時間ブロックされるのを防ぐために、シグナルハンドラなどを用いて強制的にゴルーチンを停止させます。このコミットが作成された2013年時点では、非同期プリエンプションはまだ初期段階か、現在の形では存在していなかった可能性がありますが、プリエンプションの概念自体は存在していました。
このコミットで言及されている「preemptive scheduler」は、ランタイムがロックを保持している間はプリエンプションを避けるべきであるという原則に基づいています。
m->locks
m->locks
は、Goランタイムの内部構造体 M
に含まれるフィールドで、現在のOSスレッド(M)が保持しているランタイムロックの数を追跡します。このカウンタは、ランタイムの重要な内部データ構造を保護するために使用されるロック(例えば、スケジューラのロック、メモリ管理のロックなど)が取得されるたびにインクリメントされ、解放されるたびにデクリメントされます。
m->locks
が0であることは、現在のMがランタイムの重要なロックを何も保持していないことを意味します。この状態は、スケジューラが安全にプリエンプションを実行できる条件の一つとして利用されます。
Futex (Fast Userspace Mutex)
Futex(Fast Userspace Mutex)は、Linuxカーネルが提供する同期プリミティブです。ユーザー空間のプログラムが、カーネルの介入なしに高速にロックを取得・解放できるように設計されています。競合が発生した場合にのみカーネルにシステムコールを発行し、スレッドをスリープさせたり、ウェイクアップさせたりします。Goランタイムは、ミューテックスやセマフォの実装において、OSが提供するFutexのような低レベルの同期メカニズムを利用しています。
Semaphore (セマフォ)
セマフォは、複数のプロセスやスレッドが共有リソースにアクセスする際の同期を制御するための抽象データ型です。セマフォはカウンタとして機能し、リソースの利用可能数を表します。Goランタイムでは、セマフォはゴルーチンの待機や通知、あるいは特定のランタイム内部ロックの実装に利用されることがあります。
技術的詳細
このコミットの技術的な核心は、Goランタイムのロック解除関数 runtime·unlock
における m->locks
カウンタのデクリメントのタイミング変更です。
Goランタイムには、内部的な同期のためにいくつかのロックメカニズムがあります。このコミットで修正されているのは、src/pkg/runtime/lock_futex.c
と src/pkg/runtime/lock_sema.c
にある runtime·unlock
関数です。これらはそれぞれ、Futexベースのロックとセマフォベースのロックの解除処理を担当しています。
変更前は、runtime·unlock
関数の冒頭で m->locks
をデクリメントしていました。
// 変更前 (例: lock_futex.c)
runtime·unlock(Lock *l)
{
uint32 v;
if(--m->locks < 0) // ここでm->locksをデクリメント
runtime·throw("runtime·unlock: lock count");
v = runtime·xchg((uint32*)&l->key, MUTEX_UNLOCKED); // 実際のロック解除
// ...
}
この順序だと、m->locks
が0になった直後にプリエンプションが発生する可能性があります。しかし、runtime·xchg
(exchange) やその他の操作によって実際のロック(l->key
)が MUTEX_UNLOCKED
に設定されるのは、m->locks
がデクリメントされた後です。つまり、m->locks
は0になっているが、まだロックが完全に解放されていないという一時的な状態が存在しました。
このコミットでは、if(--m->locks < 0)
のチェックとデクリメントを、実際のロック解除操作(runtime·xchg
やセマフォの解放ロジック)が完了した後に移動しています。
// 変更後 (例: lock_futex.c)
runtime·unlock(Lock *l)
{
uint32 v;
v = runtime·xchg((uint32*)&l->key, MUTEX_UNLOCKED); // 実際のロック解除
if(v == MUTEX_UNLOCKED)
runtime·throw("unlock of unlocked lock");
if(v == MUTEX_SLEEPING)
runtime·futexwakeup((uint32*)&l->key, 1);
if(--m->locks < 0) // ここに移動
runtime·throw("runtime·unlock: lock count");
}
この変更により、m->locks
が0になるのは、ロックが実際に解放され、関連する同期プリミティブ(Futexやセマフォ)の状態が更新された後になります。これにより、プリエンプティブスケジューラが m->locks == 0
を見てプリエンプションを決定する際に、ロックが完全に解放されていることが保証されます。
この修正は、Goランタイムの内部的な同期メカニズムの正確性を高め、特に高負荷時や多数のゴルーチンが同時に動作する環境での安定性向上に寄与します。
コアとなるコードの変更箇所
変更は src/pkg/runtime/lock_futex.c
と src/pkg/runtime/lock_sema.c
の2つのファイルにわたります。
src/pkg/runtime/lock_futex.c
--- a/src/pkg/runtime/lock_futex.c
+++ b/src/pkg/runtime/lock_futex.c
@@ -91,14 +91,14 @@ runtime·unlock(Lock *l)
{
uint32 v;
- if(--m->locks < 0)
- runtime·throw("runtime·unlock: lock count");
-
v = runtime·xchg((uint32*)&l->key, MUTEX_UNLOCKED);
if(v == MUTEX_UNLOCKED)
runtime·throw("unlock of unlocked lock");
if(v == MUTEX_SLEEPING)
runtime·futexwakeup((uint32*)&l->key, 1);
+
+ if(--m->locks < 0)
+ runtime·throw("runtime·unlock: lock count");
}
// One-time notifications.
src/pkg/runtime/lock_sema.c
--- a/src/pkg/runtime/lock_sema.c
+++ b/src/pkg/runtime/lock_sema.c
@@ -93,9 +93,6 @@ runtime·unlock(Lock *l)
uintptr v;
M *mp;
- if(--m->locks < 0)
- runtime·throw("runtime·unlock: lock count");
-
for(;;) {
v = (uintptr)runtime·atomicloadp((void**)&l->key);
if(v == LOCKED) {
@@ -112,6 +109,9 @@ runtime·unlock(Lock *l)
}
}
}\n+\n+\tif(--m->locks < 0)\n+\t\truntime·throw("runtime·unlock: lock count");
}\n \n // One-time notifications.
コアとなるコードの解説
両方のファイルで、runtime·unlock
関数内の if(--m->locks < 0)
という行が、関数のより後方、つまり実際のロック解除ロジックが完了した後に移動されています。
-
src/pkg/runtime/lock_futex.c
:runtime·xchg((uint32*)&l->key, MUTEX_UNLOCKED)
: これはアトミックな操作で、ロックのキーをMUTEX_UNLOCKED
に設定し、以前の値を返します。これにより、Futexベースのロックが実際に解放されます。runtime·futexwakeup((uint32*)&l->key, 1)
: もしロックがスリープ状態のゴルーチンを抱えていた場合、この関数がそれらのゴルーチンをウェイクアップします。- これらの操作が完了した後に
m->locks
がデクリメントされることで、ロックが完全に解放された状態でのみm->locks
が0になることが保証されます。
-
src/pkg/runtime/lock_sema.c
:for(;;) { ... }
ループ内のロジックは、セマフォベースのロックを解放するためのものです。これには、アトミックなロード操作 (runtime·atomicloadp
) や、必要に応じてゴルーチンをウェイクアップするロジックが含まれます。- この複雑なセマフォ解除ロジックが完全に実行され、ロックが解放された後に
m->locks
がデクリメントされるように変更されています。
この変更は、ランタイムの内部的な整合性を保つ上で非常に重要です。m->locks
は、プリエンプションの安全性を判断するための重要な指標であるため、その値がランタイムの実際のロック状態と常に同期していることが不可欠です。この修正により、プリエンプティブスケジューラが誤った情報に基づいてプリエンプションを試みるリスクが排除されます。
関連リンク
- Goのスケジューラに関する公式ドキュメントやブログ記事:
- Go's work-stealing scheduler (Go 1.5スケジューラに関する記事ですが、基本的な概念は共通しています)
- Go Scheduler: M, P, G (GoスケジューラのM, P, Gモデルに関する解説)
- Futexに関する情報:
- Goのコードレビューシステム (Gerrit) の該当CL:
参考にした情報源リンク
- Goのソースコード (
src/pkg/runtime/
) - Goの公式ブログ
- Goのドキュメント
- Goコミュニティの議論 (メーリングリストなど)
- LinuxカーネルのFutexに関するドキュメント
- 並行プログラミングに関する一般的な知識# [インデックス 16312] ファイルの概要
このコミットは、Goランタイムにおけるロック解除処理の順序を修正し、プリエンプティブスケジューラとの連携を改善することを目的としています。具体的には、m->locks
カウンタのデクリメントを、実際のロック解除操作が完了した後に行うように変更しています。これにより、ロックが完全に解除される前にゴルーチンがプリエンプトされることを防ぎ、スケジューラの正確性とシステムの安定性を向上させます。
コミット
commit 764bb36ea2be3fd9c04a0a485524a75241c21b8a
Author: Dmitriy Vyukov <dvyukov@google.com>
Date: Wed May 15 16:48:41 2013 +0400
runtime: unset m->locks after actual lock unlock
This is needed for preemptive scheduler,
it will preempt only when m->locks==0,
and we do not want to be preempted while
we have not completely unlocked the lock.
R=golang-dev, khr, iant
CC=golang-dev
https://golang.org/cl/9196047
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/764bb36ea2be3fd9c04a0a485524a75241c21b8a
元コミット内容
Goランタイムにおいて、ロック解除処理の際に m->locks
カウンタをデクリメントするタイミングを、実際のロック解除操作が完了した後に行うように変更しました。これは、プリエンプティブスケジューラが m->locks
が0の場合にのみプリエンプションを行うため、ロックが完全に解除される前にプリエンプションが発生することを避けるために必要です。
変更の背景
Goランタイムは、複数のゴルーチン(軽量スレッド)を効率的に実行するために、独自のスケジューラを持っています。このスケジューラは、ゴルーチンが長時間CPUを占有するのを防ぐために、プリエンプション(横取り)のメカニズムを備えています。プリエンプティブスケジューラは、特定の条件が満たされた場合に、実行中のゴルーチンを一時停止し、別のゴルーチンにCPUを割り当てます。
このコミットの背景にある問題は、プリエンプティブスケジューラがゴルーチンをプリエンプトする条件の一つとして、m->locks
というカウンタが0であることを見ていた点にあります。m->locks
は、現在のM(OSスレッド)が保持しているランタイムロックの数を追跡するカウンタです。ロックが保持されている間は、ランタイムの重要なデータ構造が保護されており、その間にプリエンプションが発生すると、データ競合やデッドロックなどの深刻な問題を引き起こす可能性があります。
以前の実装では、ロック解除処理の冒頭で m->locks
をデクリメントしていました。しかし、実際のロック解除操作(例えば、フューテックスやセマフォの解放)は、その後に実行されていました。このわずかな時間差の間に、プリエンプティブスケジューラが m->locks
が0になったと判断し、ロックがまだ完全に解除されていないゴルーチンをプリエンプトしてしまう可能性がありました。このような状況は、ランタイムの内部状態を不安定にし、予測不能なバグにつながる恐れがありました。
このコミットは、このタイミングの問題を解決し、ランタイムの堅牢性を高めることを目的としています。
前提知識の解説
このコミットを理解するためには、以下のGoランタイムの概念と同期プリミティブに関する知識が必要です。
Goランタイムスケジューラ (M, P, G)
Goランタイムスケジューラは、G(ゴルーチン)、M(OSスレッド)、P(プロセッサ)という3つの主要な要素で構成されています。
- G (Goroutine): Goにおける軽量な実行単位です。数KBのスタックを持ち、数百万個のゴルーチンを同時に実行できます。
- M (Machine/OS Thread): 実際のOSスレッドです。Gを実行する物理的なスレッドであり、OSのスケジューラによって管理されます。
- P (Processor): MとGを仲介する論理的なプロセッサです。Pは実行可能なGのキューを保持し、MにGを割り当てます。Pの数は通常、CPUコアの数に制限され、MがGを実行するためにPを必要とします。
スケジューラは、GをPに割り当て、PがMにGを実行させます。これにより、OSスレッドの数を制限しつつ、多数のゴルーチンを効率的に多重化して実行できます。
プリエンプティブスケジューラ
プリエンプティブスケジューラは、実行中のタスク(この場合はゴルーチン)が自発的にCPUを解放するのを待つのではなく、一定の条件や時間に基づいて強制的にCPUの利用を中断させ、別のタスクに切り替える方式です。Goランタイムでは、ゴルーチンが長時間実行され続けることによるスケジューリングの遅延を防ぐために、プリエンプションが導入されています。
Goのプリエンプションは、主に以下の2つのメカニズムで行われます。
- 協調的プリエンプション: 関数呼び出しやチャネル操作など、特定の安全なポイントでゴルーチンが自発的にスケジューラに制御を戻すことで発生します。
- 非同期プリエンプション (Async Preemption): Go 1.14以降で導入された、より強力なプリエンプションメカニズムです。これは、実行中のゴルーチンがシステムコールやGCなどの特定のランタイム操作中に長時間ブロックされるのを防ぐために、シグナルハンドラなどを用いて強制的にゴルーチンを停止させます。このコミットが作成された2013年時点では、非同期プリエンプションはまだ初期段階か、現在の形では存在していなかった可能性がありますが、プリエンプションの概念自体は存在していました。
このコミットで言及されている「preemptive scheduler」は、ランタイムがロックを保持している間はプリエンプションを避けるべきであるという原則に基づいています。
m->locks
m->locks
は、Goランタイムの内部構造体 M
に含まれるフィールドで、現在のOSスレッド(M)が保持しているランタイムロックの数を追跡します。このカウンタは、ランタイムの重要な内部データ構造を保護するために使用されるロック(例えば、スケジューラのロック、メモリ管理のロックなど)が取得されるたびにインクリメントされ、解放されるたびにデクリメントされます。
m->locks
が0であることは、現在のMがランタイムの重要なロックを何も保持していないことを意味します。この状態は、スケジューラが安全にプリエンプションを実行できる条件の一つとして利用されます。
Futex (Fast Userspace Mutex)
Futex(Fast Userspace Mutex)は、Linuxカーネルが提供する同期プリミティブです。ユーザー空間のプログラムが、カーネルの介入なしに高速にロックを取得・解放できるように設計されています。競合が発生した場合にのみカーネルにシステムコールを発行し、スレッドをスリープさせたり、ウェイクアップさせたりします。Goランタイムは、ミューテックスやセマフォの実装において、OSが提供するFutexのような低レベルの同期メカニズムを利用しています。
Semaphore (セマフォ)
セマフォは、複数のプロセスやスレッドが共有リソースにアクセスする際の同期を制御するための抽象データ型です。セマフォはカウンタとして機能し、リソースの利用可能数を表します。Goランタイムでは、セマフォはゴルーチンの待機や通知、あるいは特定のランタイム内部ロックの実装に利用されることがあります。
技術的詳細
このコミットの技術的な核心は、Goランタイムのロック解除関数 runtime·unlock
における m->locks
カウンタのデクリメントのタイミング変更です。
Goランタイムには、内部的な同期のためにいくつかのロックメカニズムがあります。このコミットで修正されているのは、src/pkg/runtime/lock_futex.c
と src/pkg/runtime/lock_sema.c
にある runtime·unlock
関数です。これらはそれぞれ、Futexベースのロックとセマフォベースのロックの解除処理を担当しています。
変更前は、runtime·unlock
関数の冒頭で m->locks
をデクリメントしていました。
// 変更前 (例: lock_futex.c)
runtime·unlock(Lock *l)
{
uint32 v;
if(--m->locks < 0) // ここでm->locksをデクリメント
runtime·throw("runtime·unlock: lock count");
v = runtime·xchg((uint32*)&l->key, MUTEX_UNLOCKED); // 実際のロック解除
// ...
}
この順序だと、m->locks
が0になった直後にプリエンプションが発生する可能性があります。しかし、runtime·xchg
(exchange) やその他の操作によって実際のロック(l->key
)が MUTEX_UNLOCKED
に設定されるのは、m->locks
がデクリメントされた後です。つまり、m->locks
は0になっているが、まだロックが完全に解放されていないという一時的な状態が存在しました。
このコミットでは、if(--m->locks < 0)
のチェックとデクリメントを、実際のロック解除操作(runtime·xchg
やセマフォの解放ロジック)が完了した後に移動しています。
// 変更後 (例: lock_futex.c)
runtime·unlock(Lock *l)
{
uint32 v;
v = runtime·xchg((uint32*)&l->key, MUTEX_UNLOCKED); // 実際のロック解除
if(v == MUTEX_UNLOCKED)
runtime·throw("unlock of unlocked lock");
if(v == MUTEX_SLEEPING)
runtime·futexwakeup((uint32*)&l->key, 1);
if(--m->locks < 0) // ここに移動
runtime·throw("runtime·unlock: lock count");
}
この変更により、m->locks
が0になるのは、ロックが実際に解放され、関連する同期プリミティブ(Futexやセマフォ)の状態が更新された後になります。これにより、プリエンプティブスケジューラが m->locks == 0
を見てプリエンプションを決定する際に、ロックが完全に解放されていることが保証されます。
この修正は、Goランタイムの内部的な同期メカニズムの正確性を高め、特に高負荷時や多数のゴルーチンが同時に動作する環境での安定性向上に寄与します。
コアとなるコードの変更箇所
変更は src/pkg/runtime/lock_futex.c
と src/pkg/runtime/lock_sema.c
の2つのファイルにわたります。
src/pkg/runtime/lock_futex.c
--- a/src/pkg/runtime/lock_futex.c
+++ b/src/pkg/runtime/lock_futex.c
@@ -91,14 +91,14 @@ runtime·unlock(Lock *l)
{
uint32 v;
- if(--m->locks < 0)
- runtime·throw("runtime·unlock: lock count");
-
v = runtime·xchg((uint32*)&l->key, MUTEX_UNLOCKED);
if(v == MUTEX_UNLOCKED)
runtime·throw("unlock of unlocked lock");
if(v == MUTEX_SLEEPING)
runtime·futexwakeup((uint32*)&l->key, 1);
+
+ if(--m->locks < 0)
+ runtime·throw("runtime·unlock: lock count");
}
// One-time notifications.
src/pkg/runtime/lock_sema.c
--- a/src/pkg/runtime/lock_sema.c
+++ b/src/pkg/runtime/lock_sema.c
@@ -93,9 +93,6 @@ runtime·unlock(Lock *l)
uintptr v;
M *mp;
- if(--m->locks < 0)
- runtime·throw("runtime·unlock: lock count");
-
for(;;) {
v = (uintptr)runtime·atomicloadp((void**)&l->key);
if(v == LOCKED) {
@@ -112,6 +109,9 @@ runtime·unlock(Lock *l)
}
}
}\n+\n+\tif(--m->locks < 0)\n+\t\truntime·throw("runtime·unlock: lock count");
}\n \n // One-time notifications.
コアとなるコードの解説
両方のファイルで、runtime·unlock
関数内の if(--m->locks < 0)
という行が、関数のより後方、つまり実際のロック解除ロジックが完了した後に移動されています。
-
src/pkg/runtime/lock_futex.c
:runtime·xchg((uint32*)&l->key, MUTEX_UNLOCKED)
: これはアトミックな操作で、ロックのキーをMUTEX_UNLOCKED
に設定し、以前の値を返します。これにより、Futexベースのロックが実際に解放されます。runtime·futexwakeup((uint32*)&l->key, 1)
: もしロックがスリープ状態のゴルーチンを抱えていた場合、この関数がそれらのゴルーチンをウェイクアップします。- これらの操作が完了した後に
m->locks
がデクリメントされることで、ロックが完全に解放された状態でのみm->locks
が0になることが保証されます。
-
src/pkg/runtime/lock_sema.c
:for(;;) { ... }
ループ内のロジックは、セマフォベースのロックを解放するためのものです。これには、アトミックなロード操作 (runtime·atomicloadp
) や、必要に応じてゴルーチンをウェイクアップするロジックが含まれます。- この複雑なセマフォ解除ロジックが完全に実行され、ロックが解放された後に
m->locks
がデクリメントされるように変更されています。
この変更は、ランタイムの内部的な整合性を保つ上で非常に重要です。m->locks
は、プリエンプションの安全性を判断するための重要な指標であるため、その値がランタイムの実際のロック状態と常に同期していることが不可欠です。この修正により、プリエンプティブスケジューラが誤った情報に基づいてプリエンプションを試みるリスクが排除されます。
関連リンク
- Goのスケジューラに関する公式ドキュメントやブログ記事:
- Go's work-stealing scheduler (Go 1.5スケジューラに関する記事ですが、基本的な概念は共通しています)
- Go Scheduler: M, P, G (GoスケジューラのM, P, Gモデルに関する解説)
- Futexに関する情報:
- Goのコードレビューシステム (Gerrit) の該当CL:
参考にした情報源リンク
- Goのソースコード (
src/pkg/runtime/
) - Goの公式ブログ
- Goのドキュメント
- Goコミュニティの議論 (メーリングリストなど)
- LinuxカーネルのFutexに関するドキュメント
- 並行プログラミングに関する一般的な知識