[インデックス 10317] ファイルの概要
このコミットは、Go言語のランタイムにタイマーサポートを追加し、time
パッケージがその新しいランタイム機能を利用するように変更するものです。主な変更点は、タイマー管理ロジックをtime
パッケージからruntime
パッケージ(C言語で実装)へ移行し、time.Sleep
、time.NewTicker
、time.NewTimer
といった時間関連の操作を単一の効率的なメカニズムの背後に統合したことです。これにより、ランタイムがゴルーチンをより適切に管理できるようになり、ガベージコレクタが未使用メモリをOSに返す際の時間遅延など、ランタイム自身のメンテナンスにもこの機能が必要となるため、タイマーロジックの一元化が図られました。
変更されたファイルは以下の通りです。
src/pkg/runtime/darwin/os.h
src/pkg/runtime/darwin/thread.c
src/pkg/runtime/freebsd/thread.c
src/pkg/runtime/linux/thread.c
src/pkg/runtime/lock_futex.c
src/pkg/runtime/lock_sema.c
src/pkg/runtime/openbsd/thread.c
src/pkg/runtime/plan9/thread.c
src/pkg/runtime/proc.c
src/pkg/runtime/runtime.h
src/pkg/runtime/time.goc
src/pkg/runtime/windows/thread.c
src/pkg/time/sleep.go
src/pkg/time/sys.go
src/pkg/time/tick.go
コミット
commit 3b860269eeb0b2d6176da5c972139b7c21d5251b
Author: Russ Cox <rsc@golang.org>
Date: Wed Nov 9 15:17:05 2011 -0500
runtime: add timer support, use for package time
This looks like it is just moving some code from
time to runtime (and translating it to C), but the
runtime can do a better job managing the goroutines,
and it needs this functionality for its own maintenance
(for example, for the garbage collector to hand back
unused memory to the OS on a time delay).
Might as well have just one copy of the timer logic,
and runtime can't depend on time, so vice versa.
It also unifies Sleep, NewTicker, and NewTimer behind
one mechanism, so that there are no claims that one
is more efficient than another. (For example, today
people recommend using time.After instead of time.Sleep
to avoid blocking an OS thread.)
Fixes #1644.
Fixes #1731.
Fixes #2190.
R=golang-dev, r, hectorchu, iant, iant, jsing, alex.brainman, dvyukov
CC=golang-dev
https://golang.org/cl/5334051
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/3b860269eeb0b2d6176da5c972139b7c21d5251b
元コミット内容
runtime: add timer support, use for package time
This looks like it is just moving some code from
time to runtime (and translating it to C), but the
runtime can do a better job managing the goroutines,
and it needs this functionality for its own maintenance
(for example, for the garbage collector to hand back
unused memory to the OS on a time delay).
Might as well have just one copy of the timer logic,
and runtime can't depend on time, so vice versa.
It also unifies Sleep, NewTicker, and NewTimer behind
one mechanism, so that there are no claims that one
is more efficient than another. (For example, today
people recommend using time.After instead of time.Sleep
to avoid blocking an OS thread.)
Fixes #1644.
Fixes #1731.
Fixes #2190.
R=golang-dev, r, hectorchu, iant, iant, jsing, alex.brainman, dvyukov
CC=golang-dev
https://golang.org/cl/5334051
変更の背景
このコミットは、Go言語のタイマー管理におけるいくつかの重要な課題と改善点を解決するために導入されました。
-
ランタイムによるゴルーチン管理の最適化: 以前は
time
パッケージが独自のタイマーロジックを持っていましたが、ランタイム(Goスケジューラ)はゴルーチンのスケジューリングとリソース管理においてより深い知識と制御を持っています。タイマーロジックをランタイムに移動することで、ゴルーチンのスリープやウェイクアップをより効率的に、かつGoスケジューラの全体的な動作と協調して行うことが可能になります。例えば、ガベージコレクタが未使用メモリをOSに返す際に時間遅延を伴う必要がある場合など、ランタイム自身の内部メンテナンスにもタイマー機能が必要とされます。ランタイムがtime
パッケージに依存することはできないため、タイマーロジックをランタイム側に一元化することが理にかなっています。 -
タイマーメカニズムの統一と効率性の向上: 以前は
time.Sleep
、time.NewTicker
、time.NewTimer
(およびtime.After
)がそれぞれ異なる、あるいは部分的に重複するメカニズムで実装されている可能性がありました。これにより、「time.After
を使う方がtime.Sleep
よりもOSスレッドをブロックしないため効率的である」といった誤解や推奨が生まれていました。このコミットは、これらすべての時間関連操作をランタイム内の単一のタイマーメカニズムの背後に統合することで、どのAPIを使っても同等の効率性が保証されるようにします。これにより、開発者はAPIの選択において効率性を気にすることなく、セマンティクスに基づいて選択できるようになります。 -
関連する既存の問題の解決: このコミットは、以下のGitHub Issueを修正します。
- Issue #1644: "Sleep should not use one thread per running call":
time.Sleep
が呼び出しごとに新しいOSスレッドを使用するべきではないという問題。これは、GoのM:Nスケジューラモデルにおいて、ゴルーチンがOSスレッドを不必要に占有することを避けるための重要な改善です。 - Issue #1731: このIssueの具体的な内容は検索結果からは特定できませんでしたが、タイマーやスケジューリングに関連する問題であった可能性が高いです。
- Issue #2190: "time.Sleep goroutines locked":
time.Sleep
がゴルーチンをロックしてしまう問題。これは、ゴルーチンの並行性を阻害し、デッドロックやパフォーマンス低下を引き起こす可能性がありました。
- Issue #1644: "Sleep should not use one thread per running call":
これらの問題は、Goの並行処理モデルとランタイムの効率性にとって重要であり、タイマーロジックのランタイムへの移行と統一は、これらの課題を根本的に解決するためのアーキテクチャ的な変更と言えます。
前提知識の解説
このコミットを理解するためには、以下のGo言語の概念とシステムプログラミングの知識が必要です。
-
Goランタイムとスケジューラ (M:Nスケジューリング):
- Goランタイムは、Goプログラムの実行を管理するシステムです。これには、ガベージコレクタ、メモリ管理、そして最も重要なスケジューラが含まれます。
- Goスケジューラは、M:Nスケジューリングモデルを採用しています。これは、M個のゴルーチン(Goの軽量な並行処理単位)を、N個のOSスレッドにマッピングするものです。通常、MはNよりもはるかに大きいです。
- スケジューラは、ゴルーチンをOSスレッド上で実行し、必要に応じてゴルーチンを一時停止(プリエンプション)したり、ブロックされたゴルーチンをOSスレッドから切り離したりして、他のゴルーチンが実行できるようにします。これにより、少数のOSスレッドで多数のゴルーチンを効率的に実行できます。
- 2011年時点のGoスケジューラは、Go 1.0のリリース前であり、現在よりもシンプルな設計でした。単一のグローバルな実行キューとグローバルロックを使用していましたが、M:Nモデルの基本的な考え方は存在していました。
-
ゴルーチン (Goroutine):
- Goにおける並行処理の基本単位です。OSスレッドよりもはるかに軽量で、数百万個のゴルーチンを同時に実行することも可能です。
- ゴルーチンはGoランタイムによって管理され、OSスレッドにマッピングされます。ゴルーチンがI/O操作などでブロックされると、ランタイムはそのゴルーチンをOSスレッドから切り離し、同じOSスレッドで別のゴルーチンを実行させることができます。
-
time
パッケージ:- Goの標準ライブラリの一部で、時間に関する機能(現在時刻の取得、時間の計測、スリープ、タイマー、ティッカーなど)を提供します。
time.Sleep(duration)
: 指定された期間、現在のゴルーチンの実行を一時停止します。time.After(duration)
: 指定された期間が経過した後に、現在の時刻を送信するチャネルを返します。これは非ブロッキングなタイムアウトの実装によく使われます。time.NewTimer(duration)
: 指定された期間が経過した後に、時刻をチャネルに送信する新しいTimer
オブジェクトを作成します。Stop()
メソッドでタイマーをキャンセルできます。time.NewTicker(duration)
: 指定された間隔で定期的に時刻をチャネルに送信する新しいTicker
オブジェクトを作成します。
-
runtime
パッケージ:- Goランタイムの低レベルな機能にアクセスするためのパッケージです。ガベージコレクタ、スケジューラ、メモリ割り当てなど、Goプログラムの実行環境を制御する機能が含まれます。
- このコミット以前は、
time
パッケージがタイマーロジックの一部を独自に実装していましたが、このコミットにより、そのロジックがruntime
パッケージに移管され、C言語で実装されることになります。
-
Futex (Fast Userspace Mutex):
- LinuxなどのUnix系OSで利用される、ユーザー空間での高速な同期プリミティブです。
- カーネルへのシステムコールを最小限に抑えることで、ロックやセマフォの取得・解放を高速化します。競合がない場合はユーザー空間で処理を完結させ、競合が発生した場合のみカーネルに介入を要求します。
futexsleep
やfutexwakeup
といった操作が提供され、特定のアドレスの値を監視し、値が変化するまでスリープしたり、スリープしているプロセスをウェイクアップしたりするために使用されます。
-
セマフォ (Semaphore):
- 複数のプロセスやスレッドが共有リソースにアクセスするのを制御するための同期メカニズムです。
- カウンタを持ち、
acquire
(P操作、待機)とrelease
(V操作、通知)の操作によってカウンタを増減させます。カウンタが0の場合にacquire
を試みると、プロセスはブロックされます。 - このコミットでは、OS固有のセマフォ実装(例: macOSの
mach_semacquire
、FreeBSD/OpenBSD/Plan 9のセマフォ関連関数、WindowsのWaitForSingleObject
)が、ゴルーチンのスリープ/ウェイクアップの低レベルなメカニズムとして利用されます。
-
Cgo (GoとCの相互運用):
- GoプログラムからC言語のコードを呼び出したり、C言語のコードからGoの関数を呼び出したりするためのメカニズムです。
- このコミットでは、
time
パッケージのタイマーロジックがC言語で記述されたruntime
パッケージに移管されるため、Cgoの概念が間接的に関わってきます。time.goc
ファイルは、GoとCのハイブリッドなコードを記述するための特殊なファイルです。
これらの知識は、コミットがGoランタイムの内部動作、特にゴルーチンのスケジューリングと時間管理にどのように影響するかを理解する上で不可欠です。
技術的詳細
このコミットの技術的詳細の核心は、Goランタイム内に新しいタイマー管理システムを構築し、既存のtime
パッケージの機能をこのシステムの上に再構築した点にあります。
-
ランタイムへのタイマーロジックの移行 (
src/pkg/runtime/time.goc
):- 以前は
time
パッケージがGoコードでタイマーヒープを管理していましたが、このコミットにより、タイマー管理の主要なロジックがsrc/pkg/runtime/time.goc
にC言語で実装されました。 time.goc
は、Goのtime
パッケージとランタイムの間のブリッジとして機能します。Nanoseconds()
、Sleep()
、startTimer()
、stopTimer()
といったGoのtime
パッケージの公開APIが、内部的にランタイムのC関数を呼び出すように変更されました。runtime·tsleep(int64 ns)
関数が導入され、指定されたナノ秒間、現在のゴルーチンをスリープさせます。これは、time.Sleep
の基盤となります。
- 以前は
-
タイマーヒープの実装:
- ランタイムは、
Timers
構造体とTimer
構造体を使用してタイマーを管理します。 Timers
構造体は、タイマーの最小ヒープ(min-heap)を保持します。このヒープは、次に期限が来るタイマーが常にルート(インデックス0)にあるようにソートされます。Timer
構造体は、個々のタイマーを表し、期限(when
)、周期(period
、定期的なタイマーの場合)、コールバック関数(f
)、および引数(arg
)を保持します。addtimer()
関数は、新しいタイマーをヒープに追加し、ヒープのプロパティを維持するためにsiftup()
を呼び出します。deltimer()
関数は、ヒープからタイマーを削除し、siftup()
とsiftdown()
を呼び出してヒープのプロパティを再構築します。siftup()
とsiftdown()
は、ヒープの要素を移動させて、ヒープの順序を維持するための標準的なヒープアルゴリズムです。
- ランタイムは、
-
timerproc
ゴルーチン:timerproc()
という専用のゴルーチンがランタイム内で実行され、すべてのタイマーイベントを処理します。- このゴルーチンは、ヒープのルートにある最も早く期限が来るタイマーの期限までスリープします。
- 期限が来ると、
timerproc
は該当するタイマーのコールバック関数を実行し、定期的なタイマーの場合は次の期限を計算してヒープを更新します。 - 新しいタイマーが追加され、それが既存のどのタイマーよりも早く期限が来る場合、
addtimer()
はtimerproc
を早期にウェイクアップさせ、新しいタイマーを処理させます。 timerproc
は、runtime·notetsleep(&timers.waitnote, delta)
を使用してスリープします。これは、指定された期間スリープするか、timers.waitnote
が通知された場合にウェイクアップする機能です。
-
OS固有の同期プリミティブの利用:
- ゴルーチンのスリープとウェイクアップの低レベルな実装には、OS固有の同期プリミティブが使用されます。
- Futex (Linux, FreeBSD):
src/pkg/runtime/lock_futex.c
および各OSのthread.c
ファイルで、runtime·futexsleep
関数がタイムアウト付きで拡張されました。これにより、指定された期間だけスリープすることが可能になります。 - セマフォ (macOS, OpenBSD, Plan 9, Windows): 各OSの
thread.c
ファイルで、runtime·semasleep
関数がタイムアウト付きで拡張されました。これにより、指定された期間だけセマフォの取得を試み、タイムアウトした場合はエラーを返すことができるようになりました。 - これらのOS固有の変更により、ランタイムはゴルーチンを効率的にブロックし、指定された時間後にウェイクアップさせることが可能になります。
-
time
パッケージの再構築:src/pkg/time/sleep.go
、src/pkg/time/sys.go
、src/pkg/time/tick.go
の各ファイルが大幅に簡素化されました。- 以前は
time
パッケージが独自のタイマーヒープ管理ロジック(container/heap
パッケージを使用)を持っていましたが、これらはすべて削除され、代わりにランタイムのstartTimer
およびstopTimer
関数を呼び出すようになりました。 time.Sleep
はruntime·tsleep
を呼び出すようになり、time.NewTimer
やtime.NewTicker
は、内部的にruntimeTimer
構造体(ランタイムのTimer
構造体とレイアウトが一致)を初期化し、startTimer
を呼び出すことでランタイムにタイマーを登録するようになりました。
この変更により、Goのタイマーシステムは、より一元化され、効率的になり、Goスケジューラの内部動作と密接に統合されることになりました。これにより、time.Sleep
やtime.After
などのAPIが、OSスレッドを不必要にブロックすることなく、ゴルーチンレベルで効率的に動作するようになります。
コアとなるコードの変更箇所
このコミットにおけるコアとなるコードの変更箇所は、主に以下のファイルと関数に集中しています。
-
src/pkg/runtime/time.goc
:- このファイルは、Goの
time
パッケージとランタイムの間のインターフェースとして機能し、タイマー管理の主要なロジックがC言語で実装されています。 static Timers timers;
:グローバルなタイマー管理構造体。static void addtimer(Timer*);
:タイマーをヒープに追加する関数。static bool deltimer(Timer*);
:タイマーをヒープから削除する関数。func Sleep(ns int64)
:Goのtime.Sleep
の実装で、runtime·tsleep
を呼び出す。func startTimer(t *Timer)
:Goのtime
パッケージからランタイムのaddtimer
を呼び出す。func stopTimer(t *Timer) (stopped bool)
:Goのtime
パッケージからランタイムのdeltimer
を呼び出す。void runtime·tsleep(int64 ns)
:指定された期間ゴルーチンをスリープさせるランタイム関数。static void timerproc(void)
:すべてのタイマーイベントを処理する専用のゴルーチン。static void siftup(int32)
、static void siftdown(int32)
:タイマーヒープを維持するための関数。
- このファイルは、Goの
-
src/pkg/runtime/runtime.h
:- ランタイムの主要なヘッダーファイルで、新しいタイマー関連の構造体と関数の宣言が追加されています。
struct Timers
:タイマーヒープと関連する状態を保持する構造体。struct Timer
:個々のタイマーの情報を保持する構造体。void runtime·tsleep(int64);
:runtime·tsleep
関数の宣言。M* runtime·newm(void);
:startm
がruntime·newm
にリネームされ、宣言が変更された。void runtime·notetsleep(Note*, int64);
:タイムアウト付きのnotesleep
関数の宣言。uintptr runtime·semacreate(void);
、int32 runtime·semasleep(int64);
、void runtime·semawakeup(M*);
:セマフォ関連関数の宣言がタイムアウト引数付きで更新された。void runtime·futexsleep(uint32*, uint32, int64);
、void runtime·futexwakeup(uint32*, uint32);
:Futex関連関数の宣言がタイムアウト引数付きで更新された。
-
OS固有の
src/pkg/runtime/*/thread.c
ファイル群:darwin/thread.c
、freebsd/thread.c
、linux/thread.c
、openbsd/thread.c
、plan9/thread.c
、windows/thread.c
- これらのファイルでは、
runtime·semasleep
(セマフォベースのOS)またはruntime·futexsleep
(FutexベースのOS)関数が、タイムアウト引数ns
(ナノ秒)を受け取るように変更されました。これにより、指定された期間だけスリープし、タイムアウトした場合は早期に復帰できるようになります。 - 例:
runtime·mach_semacquire
(Darwin),runtime·sys_umtx_op
(FreeBSD),runtime·futex
(Linux),runtime·thrsleep
(OpenBSD),runtime·plan9_semacquire
(Plan 9),runtime·WaitForSingleObject
(Windows)。
-
src/pkg/time/sleep.go
、src/pkg/time/sys.go
、src/pkg/time/tick.go
:- これらのファイルでは、以前のGo言語によるタイマーヒープ管理ロジックが削除され、代わりにランタイムの新しいタイマーAPI(
startTimer
、stopTimer
)を呼び出すように変更されました。 type runtimeTimer struct { ... }
:ランタイムのTimer
構造体とレイアウトが一致するGo側の構造体が定義された。func startTimer(*runtimeTimer)
、func stopTimer(*runtimeTimer) bool
:ランタイムのC関数を呼び出すためのGo側の宣言。time.Sleep
、time.NewTimer
、time.NewTicker
の実装が、これらの新しいランタイムAPIを使用するように書き換えられました。
- これらのファイルでは、以前のGo言語によるタイマーヒープ管理ロジックが削除され、代わりにランタイムの新しいタイマーAPI(
これらの変更は、Goのタイマーシステムを低レベルのランタイムに統合し、OS固有の効率的な同期プリミティブを活用することで、より堅牢で高性能な時間管理を実現するための基盤を形成しています。
コアとなるコードの解説
ここでは、上記のコアとなる変更箇所について、その役割と動作を詳しく解説します。
src/pkg/runtime/time.goc
このファイルは、Goのtime
パッケージがランタイムのタイマー機能を利用するための主要なインターフェースを提供します。
-
Timers
構造体とTimer
構造体:static Timers timers; // グローバルなタイマー管理構造体 struct Timers { Lock; // ミューテックス G *timerproc; // タイマー処理ゴルーチンへのポインタ bool sleeping; // timerprocがスリープ中か bool rescheduling; // timerprocが再スケジューリング中か Note waitnote; // timerprocが待機するためのNote Timer **t; // タイマーのポインタの配列(ヒープ) int32 len; // ヒープの現在の長さ int32 cap; // ヒープの容量 }; struct Timer { int32 i; // ヒープ内のインデックス // Timer wakes up at when, and then at when+period, ... (period > 0 only) // each time calling f(now, arg) in the timer goroutine, so f must be // a well-behaved function and not block. int64 when; // 期限(ナノ秒) int64 period; // 周期(ナノ秒、定期タイマーの場合) void (*f)(int64, Eface); // コールバック関数 Eface arg; // コールバック関数への引数 };
Timers
は、すべてのタイマーを管理するグローバルな構造体です。内部に最小ヒープ(t
)を持ち、最も早く期限が来るタイマーが常にヒープのルートに位置するようにします。Timer
は個々のタイマーの情報を保持します。 -
addtimer(Timer *t)
: 新しいタイマーt
をtimers
ヒープに追加します。timers
ロックを取得します。- ヒープの容量が足りない場合、新しいメモリを割り当ててヒープを拡張します。
- タイマー
t
をヒープの末尾に追加し、そのインデックスをt->i
に設定します。 siftup(t->i)
を呼び出して、ヒープのプロパティ(最小ヒープの順序)を維持します。- もし新しいタイマーがヒープのルート(インデックス0)に移動した場合、それは最も早く期限が来るタイマーであることを意味します。この場合、もし
timerproc
がスリープ中であればウェイクアップさせ、再スケジューリング中であればtimerproc
ゴルーチンをready
状態にします。 - もし
timerproc
がまだ起動していなければ、runtime·newproc1
を呼び出してtimerproc
ゴルーチンを起動します。 timers
ロックを解放します。
-
deltimer(Timer *t)
: タイマーt
をtimers
ヒープから削除します。timers
ロックを取得します。t->i
が有効なヒープインデックスであることを確認します。無効な場合はfalse
を返します。- 削除するタイマーの場所にヒープの最後の要素を移動させ、ヒープの長さを1減らします。
siftup(i)
とsiftdown(i)
を呼び出して、ヒープのプロパティを再構築します。- 削除されたタイマーのインデックスを
-1
に設定します。 timers
ロックを解放し、true
を返します。
-
runtime·tsleep(int64 ns)
: Goのtime.Sleep
の基盤となる関数です。ns
が0以下であればすぐに戻ります。Timer
構造体t
を初期化し、期限をruntime·nanotime() + ns
に設定します。- コールバック関数を
ready
(ゴルーチンをready
状態にする関数)に、引数を現在のゴルーチンg
に設定します。 addtimer(&t)
を呼び出して、このタイマーをランタイムのタイマーヒープに登録します。runtime·gosched()
を呼び出して、現在のゴルーチンを一時停止し、他のゴルーチンにCPUを譲ります。これにより、タイマーが期限切れになるまでゴルーチンは実行されません。
-
timerproc(void)
: ランタイム内で常に実行される専用のゴルーチンで、すべてのタイマーイベントを処理します。- 無限ループに入ります。
timers
ロックを取得します。- 現在の時刻
now
を取得します。 - ヒープのルートにあるタイマー(
timers.t[0]
)の期限t->when
とnow
を比較し、delta
(残り時間)を計算します。 delta
が0より大きい場合、まだ期限が来ていないのでループを抜けます。delta
が0以下の場合、タイマーの期限が来ているか、すでに過ぎています。- もし
t->period > 0
(定期タイマー)であれば、次の期限を計算し、siftdown(0)
を呼び出してヒープを更新します。 - そうでなければ(単発タイマー)、ヒープからタイマーを削除し、
t->i
を-1
に設定して削除済みとマークします。 - タイマーのコールバック関数
t->f(now, t->arg)
を実行します。
- もし
- すべてのタイマーを処理した後、
delta
が負の値(タイマーが残っていない)であれば、timerproc
はtimers.rescheduling = true
を設定し、自身のステータスをGwaiting
にしてruntime·gosched()
を呼び出し、スリープします。 delta
が正の値(次のタイマーまで時間がある)であれば、timers.sleeping = true
を設定し、runtime·noteclear(&timers.waitnote)
でwaitnote
をクリアします。timers
ロックを解放し、runtime·entersyscall()
を呼び出してシステムコールに入る準備をします。runtime·notetsleep(&timers.waitnote, delta)
を呼び出して、次のタイマーの期限までスリープします。この関数は、指定された期間スリープするか、waitnote
が通知された場合にウェイクアップします。runtime·exitsyscall()
を呼び出してシステムコールから戻ります。
-
siftup(int32 i)
とsiftdown(int32 i)
: これらは、最小ヒープのプロパティを維持するための標準的なヒープ操作です。siftup
は、要素が親よりも小さい場合に、親と交換しながら上に移動させます。siftdown
は、要素が子よりも大きい場合に、小さい方の子と交換しながら下に移動させます。
src/pkg/runtime/runtime.h
このファイルでは、Timers
とTimer
構造体の定義、および新しいランタイム関数のプロトタイプが追加されています。特に重要なのは、semasleep
やfutexsleep
といったOS固有の同期プリミティブが、タイムアウト引数を受け取るように変更された点です。これにより、ランタイムは指定された期間だけ待機し、タイムアウトした場合は早期に復帰できるようになります。
OS固有のsrc/pkg/runtime/*/thread.c
ファイル群
これらのファイルは、各OSにおける低レベルなスリープ/ウェイクアップメカニズムの実装を更新します。
- 例:
src/pkg/runtime/linux/thread.c
のruntime·futexsleep
:void runtime·futexsleep(uint32 *addr, uint32 val, int64 ns) { Timespec ts, *tsp; if(ns < 0) tsp = nil; // タイムアウトなし(無限待機) else { ts.tv_sec = ns/1000000000LL; ts.tv_nsec = ns%1000000000LL; // Avoid overflow if(ts.tv_sec > 1<<30) ts.tv_sec = 1<<30; tsp = &ts; // タイムアウト指定 } // ... (既存のコメント) ... runtime·futex(addr, FUTEX_WAIT, val, tsp, nil, 0); }
ns
引数が追加され、Timespec
構造体を使ってタイムアウト時間を設定できるようになりました。runtime·futex
システムコールにこのタイムアウト情報が渡されます。他のOSのthread.c
ファイルでも同様に、セマフォやイベントオブジェクトの待機関数にタイムアウト引数が追加されています。
src/pkg/time/sleep.go
、src/pkg/time/sys.go
、src/pkg/time/tick.go
これらのファイルは、Goのtime
パッケージのユーザー向けAPIの実装を簡素化します。
-
type runtimeTimer struct { ... }
: Go側でランタイムのTimer
構造体とメモリレイアウトが一致するruntimeTimer
構造体を定義します。これにより、GoとCの間でタイマー情報を効率的に受け渡すことができます。 -
func startTimer(*runtimeTimer)
とfunc stopTimer(*runtimeTimer) bool
: これらはGoの関数ですが、実際にはCgoを介してランタイムのaddtimer
とdeltimer
C関数を呼び出すためのラッパーです。 -
func Sleep(ns int64)
(insrc/pkg/time/sys.go
): 以前はGoでスリープロジックを実装していましたが、このコミットにより、単にruntime·tsleep(ns)
を呼び出すだけになりました。 -
func NewTimer(ns int64) *Timer
(insrc/pkg/time/sleep.go
): 新しいTimer
を作成する際に、内部的にruntimeTimer
を初期化し、startTimer(&t.r)
を呼び出してランタイムにタイマーを登録するようになりました。 -
func NewTicker(ns int64) *Ticker
(insrc/pkg/time/tick.go
):NewTimer
と同様に、内部的にruntimeTimer
を初期化し、startTimer(&t.r)
を呼び出してランタイムに定期タイマーを登録するようになりました。
これらの変更により、Goのタイマーシステムは、低レベルのランタイムに完全に統合され、OSの効率的な同期メカニズムを直接利用するようになりました。これにより、Goの並行処理モデルにおける時間管理のパフォーマンスと信頼性が大幅に向上しました。
関連リンク
- Go Issue #1644: Sleep should not use one thread per running call
- https://github.com/golang/go/issues/1644
- Go Issue #1731: (具体的な内容は不明だが、タイマー/スケジューリング関連の可能性)
- https://github.com/golang/go/issues/1731
- Go Issue #2190: time.Sleep goroutines locked
- https://github.com/golang/go/issues/2190
- Gerrit Change-Id: https://golang.org/cl/5334051
参考にした情報源リンク
- Go issue 1644: https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQH3g_qlJQRPIvFFPWRPhEtZh8piXEMf025lwG3Dv1HP9l91zPS_ZQCPV5frVlXdBZHIgBP0QLlwippkbyXem_0C1oMfMrYBLoTCR_gz4bzHPjIcJEdJqtZMzeA2WdKd8TCb7dA=
- Go issue 1731: https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQGnLPEY-v4DMpvZyHbb1Pdapv2KdRi81c4W2FuwQFX9PIjVJKDabu5qrR4cUBoDUekPp3T1fIMY88IdScaMYgGIyezqDBxAUsVvPB3On9pHcfVc_7CJUAf_J3t2sZS0JcvjpRrpcyuhavDKnORiG8Nt6SZGlA=
- Go issue 2190: https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQEwwBg_-RYHGawCo64pfwYQ2Y_LtGm90Bu-Ow5Kzr271MxNdxJvuxtMv2o-3S98pO8y_ZmCOw6G_mGqeLNJpjnBSlORLtDUokgEZDVKqu9tCb24QwpvSE9OeK3oHogBdn05aEU=
- Go runtime timer 2011: https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQEsha-GEMPwBbac_ydjThpdmz2fi7ngSu08GCDdSKjmrnN-JrZh5nrXGyOTonFIK4Wb2AwiIHcUrxO4ymLOOe0tkuxJhRdJzl7g9XDNw3mpbclsPh3Q4mm0LQ==
- Go scheduler 2011: https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQEmCePAbEv5_vIaUMYDK7B5Us2mz-U72cbOq5KznESy3i7avuHbHJb6NWsqp01iHuLQMSFd-0ojm0LFHbvbjQV41kmuw0DBIi_5LOIZnfH4YGfdVMfDGNZbP4k-DNtlMK4uOltcn53ZLaQxBJa0O3SRoaGn8bw=
- Go time.After vs time.Sleep efficiency: https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQGPUrMk1SpODbifpBOOmVqN2QCc0aBR8m3kEZkaxXMVE8dEa6wNdbY7BsKM-6olKZ1VR4WziWo-g4_gkdnJ_iMBPtsgMRfuHql6ObY8msqgYzTn24-0Har4OmlCLv8qUoniXym9FgcRic2SQYQM-peo