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

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

このコミットは、Go言語のランタイムにタイマーサポートを追加し、timeパッケージがその新しいランタイム機能を利用するように変更するものです。主な変更点は、タイマー管理ロジックをtimeパッケージからruntimeパッケージ(C言語で実装)へ移行し、time.Sleeptime.NewTickertime.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言語のタイマー管理におけるいくつかの重要な課題と改善点を解決するために導入されました。

  1. ランタイムによるゴルーチン管理の最適化: 以前はtimeパッケージが独自のタイマーロジックを持っていましたが、ランタイム(Goスケジューラ)はゴルーチンのスケジューリングとリソース管理においてより深い知識と制御を持っています。タイマーロジックをランタイムに移動することで、ゴルーチンのスリープやウェイクアップをより効率的に、かつGoスケジューラの全体的な動作と協調して行うことが可能になります。例えば、ガベージコレクタが未使用メモリをOSに返す際に時間遅延を伴う必要がある場合など、ランタイム自身の内部メンテナンスにもタイマー機能が必要とされます。ランタイムがtimeパッケージに依存することはできないため、タイマーロジックをランタイム側に一元化することが理にかなっています。

  2. タイマーメカニズムの統一と効率性の向上: 以前はtime.Sleeptime.NewTickertime.NewTimer(およびtime.After)がそれぞれ異なる、あるいは部分的に重複するメカニズムで実装されている可能性がありました。これにより、「time.Afterを使う方がtime.SleepよりもOSスレッドをブロックしないため効率的である」といった誤解や推奨が生まれていました。このコミットは、これらすべての時間関連操作をランタイム内の単一のタイマーメカニズムの背後に統合することで、どのAPIを使っても同等の効率性が保証されるようにします。これにより、開発者はAPIの選択において効率性を気にすることなく、セマンティクスに基づいて選択できるようになります。

  3. 関連する既存の問題の解決: このコミットは、以下の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がゴルーチンをロックしてしまう問題。これは、ゴルーチンの並行性を阻害し、デッドロックやパフォーマンス低下を引き起こす可能性がありました。

これらの問題は、Goの並行処理モデルとランタイムの効率性にとって重要であり、タイマーロジックのランタイムへの移行と統一は、これらの課題を根本的に解決するためのアーキテクチャ的な変更と言えます。

前提知識の解説

このコミットを理解するためには、以下のGo言語の概念とシステムプログラミングの知識が必要です。

  1. Goランタイムとスケジューラ (M:Nスケジューリング):

    • Goランタイムは、Goプログラムの実行を管理するシステムです。これには、ガベージコレクタ、メモリ管理、そして最も重要なスケジューラが含まれます。
    • Goスケジューラは、M:Nスケジューリングモデルを採用しています。これは、M個のゴルーチン(Goの軽量な並行処理単位)を、N個のOSスレッドにマッピングするものです。通常、MはNよりもはるかに大きいです。
    • スケジューラは、ゴルーチンをOSスレッド上で実行し、必要に応じてゴルーチンを一時停止(プリエンプション)したり、ブロックされたゴルーチンをOSスレッドから切り離したりして、他のゴルーチンが実行できるようにします。これにより、少数のOSスレッドで多数のゴルーチンを効率的に実行できます。
    • 2011年時点のGoスケジューラは、Go 1.0のリリース前であり、現在よりもシンプルな設計でした。単一のグローバルな実行キューとグローバルロックを使用していましたが、M:Nモデルの基本的な考え方は存在していました。
  2. ゴルーチン (Goroutine):

    • Goにおける並行処理の基本単位です。OSスレッドよりもはるかに軽量で、数百万個のゴルーチンを同時に実行することも可能です。
    • ゴルーチンはGoランタイムによって管理され、OSスレッドにマッピングされます。ゴルーチンがI/O操作などでブロックされると、ランタイムはそのゴルーチンをOSスレッドから切り離し、同じOSスレッドで別のゴルーチンを実行させることができます。
  3. timeパッケージ:

    • Goの標準ライブラリの一部で、時間に関する機能(現在時刻の取得、時間の計測、スリープ、タイマー、ティッカーなど)を提供します。
    • time.Sleep(duration): 指定された期間、現在のゴルーチンの実行を一時停止します。
    • time.After(duration): 指定された期間が経過した後に、現在の時刻を送信するチャネルを返します。これは非ブロッキングなタイムアウトの実装によく使われます。
    • time.NewTimer(duration): 指定された期間が経過した後に、時刻をチャネルに送信する新しいTimerオブジェクトを作成します。Stop()メソッドでタイマーをキャンセルできます。
    • time.NewTicker(duration): 指定された間隔で定期的に時刻をチャネルに送信する新しいTickerオブジェクトを作成します。
  4. runtimeパッケージ:

    • Goランタイムの低レベルな機能にアクセスするためのパッケージです。ガベージコレクタ、スケジューラ、メモリ割り当てなど、Goプログラムの実行環境を制御する機能が含まれます。
    • このコミット以前は、timeパッケージがタイマーロジックの一部を独自に実装していましたが、このコミットにより、そのロジックがruntimeパッケージに移管され、C言語で実装されることになります。
  5. Futex (Fast Userspace Mutex):

    • LinuxなどのUnix系OSで利用される、ユーザー空間での高速な同期プリミティブです。
    • カーネルへのシステムコールを最小限に抑えることで、ロックやセマフォの取得・解放を高速化します。競合がない場合はユーザー空間で処理を完結させ、競合が発生した場合のみカーネルに介入を要求します。
    • futexsleepfutexwakeupといった操作が提供され、特定のアドレスの値を監視し、値が変化するまでスリープしたり、スリープしているプロセスをウェイクアップしたりするために使用されます。
  6. セマフォ (Semaphore):

    • 複数のプロセスやスレッドが共有リソースにアクセスするのを制御するための同期メカニズムです。
    • カウンタを持ち、acquire(P操作、待機)とrelease(V操作、通知)の操作によってカウンタを増減させます。カウンタが0の場合にacquireを試みると、プロセスはブロックされます。
    • このコミットでは、OS固有のセマフォ実装(例: macOSのmach_semacquire、FreeBSD/OpenBSD/Plan 9のセマフォ関連関数、WindowsのWaitForSingleObject)が、ゴルーチンのスリープ/ウェイクアップの低レベルなメカニズムとして利用されます。
  7. Cgo (GoとCの相互運用):

    • GoプログラムからC言語のコードを呼び出したり、C言語のコードからGoの関数を呼び出したりするためのメカニズムです。
    • このコミットでは、timeパッケージのタイマーロジックがC言語で記述されたruntimeパッケージに移管されるため、Cgoの概念が間接的に関わってきます。time.gocファイルは、GoとCのハイブリッドなコードを記述するための特殊なファイルです。

これらの知識は、コミットがGoランタイムの内部動作、特にゴルーチンのスケジューリングと時間管理にどのように影響するかを理解する上で不可欠です。

技術的詳細

このコミットの技術的詳細の核心は、Goランタイム内に新しいタイマー管理システムを構築し、既存のtimeパッケージの機能をこのシステムの上に再構築した点にあります。

  1. ランタイムへのタイマーロジックの移行 (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の基盤となります。
  2. タイマーヒープの実装:

    • ランタイムは、Timers構造体とTimer構造体を使用してタイマーを管理します。
    • Timers構造体は、タイマーの最小ヒープ(min-heap)を保持します。このヒープは、次に期限が来るタイマーが常にルート(インデックス0)にあるようにソートされます。
    • Timer構造体は、個々のタイマーを表し、期限(when)、周期(period、定期的なタイマーの場合)、コールバック関数(f)、および引数(arg)を保持します。
    • addtimer()関数は、新しいタイマーをヒープに追加し、ヒープのプロパティを維持するためにsiftup()を呼び出します。
    • deltimer()関数は、ヒープからタイマーを削除し、siftup()siftdown()を呼び出してヒープのプロパティを再構築します。
    • siftup()siftdown()は、ヒープの要素を移動させて、ヒープの順序を維持するための標準的なヒープアルゴリズムです。
  3. timerprocゴルーチン:

    • timerproc()という専用のゴルーチンがランタイム内で実行され、すべてのタイマーイベントを処理します。
    • このゴルーチンは、ヒープのルートにある最も早く期限が来るタイマーの期限までスリープします。
    • 期限が来ると、timerprocは該当するタイマーのコールバック関数を実行し、定期的なタイマーの場合は次の期限を計算してヒープを更新します。
    • 新しいタイマーが追加され、それが既存のどのタイマーよりも早く期限が来る場合、addtimer()timerprocを早期にウェイクアップさせ、新しいタイマーを処理させます。
    • timerprocは、runtime·notetsleep(&timers.waitnote, delta)を使用してスリープします。これは、指定された期間スリープするか、timers.waitnoteが通知された場合にウェイクアップする機能です。
  4. 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固有の変更により、ランタイムはゴルーチンを効率的にブロックし、指定された時間後にウェイクアップさせることが可能になります。
  5. timeパッケージの再構築:

    • src/pkg/time/sleep.gosrc/pkg/time/sys.gosrc/pkg/time/tick.goの各ファイルが大幅に簡素化されました。
    • 以前はtimeパッケージが独自のタイマーヒープ管理ロジック(container/heapパッケージを使用)を持っていましたが、これらはすべて削除され、代わりにランタイムのstartTimerおよびstopTimer関数を呼び出すようになりました。
    • time.Sleepruntime·tsleepを呼び出すようになり、time.NewTimertime.NewTickerは、内部的にruntimeTimer構造体(ランタイムのTimer構造体とレイアウトが一致)を初期化し、startTimerを呼び出すことでランタイムにタイマーを登録するようになりました。

この変更により、Goのタイマーシステムは、より一元化され、効率的になり、Goスケジューラの内部動作と密接に統合されることになりました。これにより、time.Sleeptime.AfterなどのAPIが、OSスレッドを不必要にブロックすることなく、ゴルーチンレベルで効率的に動作するようになります。

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

このコミットにおけるコアとなるコードの変更箇所は、主に以下のファイルと関数に集中しています。

  1. 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):タイマーヒープを維持するための関数。
  2. src/pkg/runtime/runtime.h:

    • ランタイムの主要なヘッダーファイルで、新しいタイマー関連の構造体と関数の宣言が追加されています。
    • struct Timers:タイマーヒープと関連する状態を保持する構造体。
    • struct Timer:個々のタイマーの情報を保持する構造体。
    • void runtime·tsleep(int64);runtime·tsleep関数の宣言。
    • M* runtime·newm(void);startmruntime·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関連関数の宣言がタイムアウト引数付きで更新された。
  3. OS固有のsrc/pkg/runtime/*/thread.cファイル群:

    • darwin/thread.cfreebsd/thread.clinux/thread.copenbsd/thread.cplan9/thread.cwindows/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)。
  4. src/pkg/time/sleep.gosrc/pkg/time/sys.gosrc/pkg/time/tick.go:

    • これらのファイルでは、以前のGo言語によるタイマーヒープ管理ロジックが削除され、代わりにランタイムの新しいタイマーAPI(startTimerstopTimer)を呼び出すように変更されました。
    • type runtimeTimer struct { ... }:ランタイムのTimer構造体とレイアウトが一致するGo側の構造体が定義された。
    • func startTimer(*runtimeTimer)func stopTimer(*runtimeTimer) bool:ランタイムのC関数を呼び出すためのGo側の宣言。
    • time.Sleeptime.NewTimertime.NewTickerの実装が、これらの新しいランタイム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): 新しいタイマーttimersヒープに追加します。

    1. timersロックを取得します。
    2. ヒープの容量が足りない場合、新しいメモリを割り当ててヒープを拡張します。
    3. タイマーtをヒープの末尾に追加し、そのインデックスをt->iに設定します。
    4. siftup(t->i)を呼び出して、ヒープのプロパティ(最小ヒープの順序)を維持します。
    5. もし新しいタイマーがヒープのルート(インデックス0)に移動した場合、それは最も早く期限が来るタイマーであることを意味します。この場合、もしtimerprocがスリープ中であればウェイクアップさせ、再スケジューリング中であればtimerprocゴルーチンをready状態にします。
    6. もしtimerprocがまだ起動していなければ、runtime·newproc1を呼び出してtimerprocゴルーチンを起動します。
    7. timersロックを解放します。
  • deltimer(Timer *t): タイマーttimersヒープから削除します。

    1. timersロックを取得します。
    2. t->iが有効なヒープインデックスであることを確認します。無効な場合はfalseを返します。
    3. 削除するタイマーの場所にヒープの最後の要素を移動させ、ヒープの長さを1減らします。
    4. siftup(i)siftdown(i)を呼び出して、ヒープのプロパティを再構築します。
    5. 削除されたタイマーのインデックスを-1に設定します。
    6. timersロックを解放し、trueを返します。
  • runtime·tsleep(int64 ns): Goのtime.Sleepの基盤となる関数です。

    1. nsが0以下であればすぐに戻ります。
    2. Timer構造体tを初期化し、期限をruntime·nanotime() + nsに設定します。
    3. コールバック関数をready(ゴルーチンをready状態にする関数)に、引数を現在のゴルーチンgに設定します。
    4. addtimer(&t)を呼び出して、このタイマーをランタイムのタイマーヒープに登録します。
    5. runtime·gosched()を呼び出して、現在のゴルーチンを一時停止し、他のゴルーチンにCPUを譲ります。これにより、タイマーが期限切れになるまでゴルーチンは実行されません。
  • timerproc(void): ランタイム内で常に実行される専用のゴルーチンで、すべてのタイマーイベントを処理します。

    1. 無限ループに入ります。
    2. timersロックを取得します。
    3. 現在の時刻nowを取得します。
    4. ヒープのルートにあるタイマー(timers.t[0])の期限t->whennowを比較し、delta(残り時間)を計算します。
    5. deltaが0より大きい場合、まだ期限が来ていないのでループを抜けます。
    6. deltaが0以下の場合、タイマーの期限が来ているか、すでに過ぎています。
      • もしt->period > 0(定期タイマー)であれば、次の期限を計算し、siftdown(0)を呼び出してヒープを更新します。
      • そうでなければ(単発タイマー)、ヒープからタイマーを削除し、t->i-1に設定して削除済みとマークします。
      • タイマーのコールバック関数t->f(now, t->arg)を実行します。
    7. すべてのタイマーを処理した後、deltaが負の値(タイマーが残っていない)であれば、timerproctimers.rescheduling = trueを設定し、自身のステータスをGwaitingにしてruntime·gosched()を呼び出し、スリープします。
    8. deltaが正の値(次のタイマーまで時間がある)であれば、timers.sleeping = trueを設定し、runtime·noteclear(&timers.waitnote)waitnoteをクリアします。
    9. timersロックを解放し、runtime·entersyscall()を呼び出してシステムコールに入る準備をします。
    10. runtime·notetsleep(&timers.waitnote, delta)を呼び出して、次のタイマーの期限までスリープします。この関数は、指定された期間スリープするか、waitnoteが通知された場合にウェイクアップします。
    11. runtime·exitsyscall()を呼び出してシステムコールから戻ります。
  • siftup(int32 i)siftdown(int32 i): これらは、最小ヒープのプロパティを維持するための標準的なヒープ操作です。

    • siftupは、要素が親よりも小さい場合に、親と交換しながら上に移動させます。
    • siftdownは、要素が子よりも大きい場合に、小さい方の子と交換しながら下に移動させます。

src/pkg/runtime/runtime.h

このファイルでは、TimersTimer構造体の定義、および新しいランタイム関数のプロトタイプが追加されています。特に重要なのは、semasleepfutexsleepといったOS固有の同期プリミティブが、タイムアウト引数を受け取るように変更された点です。これにより、ランタイムは指定された期間だけ待機し、タイムアウトした場合は早期に復帰できるようになります。

OS固有のsrc/pkg/runtime/*/thread.cファイル群

これらのファイルは、各OSにおける低レベルなスリープ/ウェイクアップメカニズムの実装を更新します。

  • 例: src/pkg/runtime/linux/thread.cruntime·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.gosrc/pkg/time/sys.gosrc/pkg/time/tick.go

これらのファイルは、Goのtimeパッケージのユーザー向けAPIの実装を簡素化します。

  • type runtimeTimer struct { ... }: Go側でランタイムのTimer構造体とメモリレイアウトが一致するruntimeTimer構造体を定義します。これにより、GoとCの間でタイマー情報を効率的に受け渡すことができます。

  • func startTimer(*runtimeTimer)func stopTimer(*runtimeTimer) bool: これらはGoの関数ですが、実際にはCgoを介してランタイムのaddtimerdeltimerC関数を呼び出すためのラッパーです。

  • func Sleep(ns int64) (in src/pkg/time/sys.go): 以前はGoでスリープロジックを実装していましたが、このコミットにより、単にruntime·tsleep(ns)を呼び出すだけになりました。

  • func NewTimer(ns int64) *Timer (in src/pkg/time/sleep.go): 新しいTimerを作成する際に、内部的にruntimeTimerを初期化し、startTimer(&t.r)を呼び出してランタイムにタイマーを登録するようになりました。

  • func NewTicker(ns int64) *Ticker (in src/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