For Gauche 0.9.10


Next: , Previous: , Up: ライブラリモジュール - Gauche拡張モジュール   [Contents][Index]

9.33 gauche.threads - スレッド

Gaucheでは、コンパイル時に有効にしていれば、POSIXスレッド(pthreads)か Windowsスレッド上に構築されるスレッドを使うことができます。

Module: gauche.threads

スレッドを扱うAPIを提供します。コンパイル時にスレッドのサポートを 指定したか否かに関わらず、このモジュールを’use’することができます。 スレッドがサポートされていない場合は、多くのスレッド関連の手続きは 単に“not supported”エラーを通知するだけです。

pthreadが使えるかどうかによって実行するコードを切り替えたい場合は、 cond-expandフォームでgauche.sys.threadsという feature identifierを使うことができます (機能条件式参照)。

(cond-expand
 [gauche.sys.threads
   ;; Thread APIを使うコード (gauche.threadsはこの時点で
   ;; 自動的にロードされます).
  ]
 [else
   ;; Thread APIを使わないコード
  ])

また、pthreadプラットフォームとWindowsスレッドプラットフォームで それぞれ、gauche.sys.pthreadsgauche.sys.wthreads というfeature identifierも定義されています。 ただ、Schemeレベルではこれら下位の実装の違いを意識する必要は ほとんど無いでしょう。スレッドサポートの有無によりコードを切り替える時は、 gauche.sys.threadsを使うのが良いでしょう。

コンパイル時ではなく実行時にスレッドが有効かどうかをチェックするためには、 次の手続きを使います。

Function: gauche-thread-type

{gauche.threads} サポートされているスレッドのタイプを表すシンボルを返します。 以下のいずれかのシンボルが返されます。

none

スレッドはサポートされていません。

pthread

スレッドはPOSIXのpthreadsを使って実装されています。

win32

スレッドはWin32スレッドを使って実装されています。

(註:pthreadプラットフォームではシンボルpthreadではなく pthreadsを返すべきでした。そうすれば、 gauche-thread-typeの返り値はコンフィグレーション時に --enable-threadsに与えた値と対応するものになっていたでしょう。 互換性のために、残念ながらこの見過ごしは修正されないでしょう。

SchemeレベルのスレッドAPIはSRFI-18、“マルチスレッドサポート” (https://srfi.schemers.org/srfi-18/srfi-18.html)を満たし、Gaucheのオブジェクトの インターフェースでラップされます。


Next: , Previous: , Up: スレッド   [Contents][Index]

9.33.1 スレッドプログラミングTips

Gaucheのスレッドをどう使うか

スレッドのAPIは外見上シンプルでポータブルに見えますが、 その機能の潜在的な力を活用するためには、スレッドがどのように 実装されているかを知る必要があります。 いくつかの言語では言語組み込みの機能としてスレッドをサポートし、 プログラマによるスレッドの利用を推奨しています。 しかし、多くの場合、実現したいアルゴリズムをスレッドを使わずに 実装する方法があります。 スレッドを使うことの利点と欠点を、そのスレッドがシステムによってどのように 実現されているかを考慮した上で比較する必要があります。

Gaucheでは、スレッドを使う一番の目的は、他の方法で表現することが 難しい、プリエンプティブなスケジューリングを必要とする プログラムを書くことです。プリエンプティブなスレッドは、 例えば、中断できないブロッキングI/Oを行うモジュールを 使わなければならないときや、実行時間の分からない計算に 割り込みを行いたいときなどに必要となります。

それぞれのGaucheのスレッドには、個別の仮想マシンが割り当てられ、 専用のPOSIXスレッドにより実行されます。したがって、コンテキスト スイッチのオーバヘッドは、ネイティブスレッドと同等です。 しかし、スレッドの生成は、例えばcall/ccによる軽量スレッドよりは ずっとコストのかかる処理です。 このように、Gaucheのプリエンプティブなスレッドは、 きめ細かい計算のために幾千ものスレッドを生成したいアプリケーション 向けではありません

推奨される使用方法は、いわゆる“スレッドプール”と呼ばれる テクニックです。つまり、スレッドの集合を作って長時間それを 保持し、必要になったときにジョブをそこへディスパッチする というものです。Gaucheはcontrol.thread-poolモジュール (スレッドプール参照) でスレッドプールの実装を提供しています。

プリエンプティブなスレッドには他にも難しい点があり、 しばしばネイティブなプリエンプティブスレッドよりも より良くフィットする代替策があります (例えばhttps://www-sop.inria.fr/mimosa/rp/FairThreads/html/FairThreads.html参照)。

もちろん、これらのテクニックはネイティブスレッドとは相互排他ではありません。 例えば、“スレッドプール”テクニックと一緒にディスパッチャを使うこともできます。 それらの機能を実現するために、ネイティブスレッドが唯一の方法ではないということを 心に留め置いて下さい。

スレッドボディ内で捕捉されないエラー

シングルスレッドのプログラムが予期せぬ(捕捉されない)エラーを起こした場合、 デフォルトではGaucheはエラーメッセージとスタックトレースを表示します。 ところが、スレッドで捕捉されないエラーが生じスレッドが終了しても、 何も表示されません。

それはこういうわけです。スレッド本体でエラーが起き、それがスレッド中で処理されなければ、 スレッドは終了し、エラー自体はそのスレッドの終了を待つスレッドへと伝達される仕組みだからです。 エラーで終了したスレッドに対してthread-join!を呼んだ時点で、 元のエラーを<uncaught-exception>でラップしたエラーが投げられます。 この動作はSRFI-18で規定されています。

新たなスレッドを一時的な計算のために走らせて、その結果をthread-join!で 受け取るという使い方なら、この動作は便利です。「親スレッド」でまとめてエラーを 捕捉できるからです。けれどもスレッドをずっとループさせてジョブを次々と処理する、といった 用法ではthread-join!は呼ばれるとは限らず、この仕様は落とし穴になり得ます。 スレッドがエラーで死んだのに気づかないかもしれないからです。 (エラーで終了したスレッドがthread-join!されることなくGC対象になった場合は 警告が出力されます。しかし、GCがどのタイミングで起きるかはわかりません。)

そういう使い方をするスレッドについては、本体を常にguardで包んで エラーを明示的に処理すべきです。例えばエラーメッセージとスタックトレースを出したければ report-errorを呼びます。

(thread-start!
 (make-thread (^[] (guard (e [else (report-error e) #f])
                     ... thread body ...))))

スレッドでの例外の処理について詳しくはスレッド例外を参照してください。

註: 0.9.5の時点では、guardのエラーハンドラ節が 末尾コンテクストで呼ばれないというバグがあります。 例えば次のコードはSchemeでは本来スタックを消費せずにループするはずですが、 現在のGaucheではスタックを徐々に食いつぶしてしまいます。

(thread-start!
 (make-thread (^[] (let loop ()
                     (guard (e [else (report-error e) (loop)])
                       ... thread body ...)))))

当面は回避策として、loopの呼び出しをguardの外側に出してください。

(thread-start!
 (make-thread (^[] (let loop ()
                     (guard (e [else (report-error e)])
                       ... thread body ...)
                     (loop)))))

Next: , Previous: , Up: スレッド   [Contents][Index]

9.33.2 スレッド手続き

Builtin Class: <thread>

スレッドを表すクラスです。それぞれのスレッドは、POSIXスレッドにより 評価される関連付けられた手続きを持ちます。手続きが正常に戻ると、 その結果は内部的な“結果”スロットに格納され、thread-join!により 取得することができます。例外が投げられるか thread-terminate!により終了されるかで、手続きが異常終了すると、 例外条件が内部的な“結果としての例外”スロットに格納され、 その例外条件は終了したスレッドに対してthread-join!を 呼ぶスレッドへと渡されます。

それぞれのスレッドは独自の動的環境と動的なハンドラスタックを持っています。 あるスレッドが生成されると、その動的な環境は生成者の動的な環境によって 初期化されます。そのスレッドの動的なハンドラスタックは初期においては空です。

スレッドは以下の4つの状態のうちのひとつを取ります。thread-state手続きで スレッドの状態を調べることができます。

new

まだ作られたばかりで起動されてない状態です。make-threadが返すスレッドは この状態です。スレッドがひとたび起動されると、そのスレッドがこの状態に戻ることは 決してありません。 この時点ではPOSIXスレッドはまだ作られません。thread-start!によって POSIXスレッドが作られ、Gaucheのスレッドを実行します。

runnable

thread-start!によって起動されたスレッドはこの状態になります。 スレッドがシステムコールによるブロックされている時もその状態はrunnable であることに注意してください。

stopped

スレッドがthread-stop!によって止められるとこの状態になります。 この状態のスレッドはthread-cont!によって再びrunnableになり、 止められた時点から実行を再開することができます。

terminated

割り当てられたコードの実行が終了したり、thread-terminate!によって 強制的に終了させられた時に、スレッドはこの状態になります。 一度この状態になると他の状態に遷移することはありません。

複数のスレッドで共有されるリソースへのアクセスは、同期化プリミティブにより 明示的に保護されなければなりません。同期プリミティブ参照。

ポートへのアクセスはGaucheによりシリアライズされます。 複数のスレッドが1つのポートへの書き込みを試みた場合、それらの出力は 混じることもありますが、失われる出力はなく、そのポートのステータスは 一貫性が保たれます。複数のスレッドが1つのポートからの読み込みを試みた 場合、1つの読み込みプリミティブ(例えば、readread-charread-lineなど)がアトミックに実行されます。

シグナルハンドラは全てのスレッドで共有されますが、それぞれのスレッドは 独自のシグナルマスクを持ちます。詳細は、シグナルとスレッドを参照。

スレッドオブジェクトは以下の外部スロットを持ちます。

Instance Variable of <thread>: name

スレッドに関連付けられる名前。 これは単にアプリケーションにとっての便宜を図るためのものです。 原始となるスレッドは“root”という名前を持ちます。

Instance Variable of <thread>: specific

アプリケーションが使うスレッドローカルなスロット。

Function: current-thread

[SRFI-18], [SRFI-21] {gauche.threads} 現在のスレッドを返します。

Function: thread? obj

[SRFI-18], [SRFI-21] {gauche.threads} objがスレッドなら#t、そうでなければ#fを返します。

Function: make-thread thunk :optional name

[SRFI-18], [SRFI-21] {gauche.threads} thunkを実行するための新しいスレッドを生成して返します。 そのスレッドの実行を開始するには、thread-start!を呼ぶ必要があります。 thunkの実行結果は、thread-join!を呼ぶことで回収できます。

オプション引数nameを与えることで、そのスレッドに名前を与えることができます。

作成されたスレッドは、呼び出したスレッドのシグナルマスクを継承し (シグナルとスレッド参照)、また呼び出したスレッドの持つ その時点でのパラメータのコピーを受けとります。

これらの初期化操作以外に、作られるスレッドと呼び出したスレッド間の関係は ありません。Unixのプロセスのような親子関係があるわけではないのです。 どのスレッドも、他のスレッドに対してthread-join!を発行 して結果を受け取ることができます。 もし誰もthread-join!を発行せず、また作られたスレッドに対する 参照を保持していなかった場合、スレッドは実行が終了した後にガベージコレクトされます。

もしスレッドが捕捉されない例外のために実行を終了し、その結果がthread-join!で 回収されなかった場合、標準エラーポートに“thread dies a lonely death” という 警告メッセージが出力されます。そのようなケースは通常何らかのコーディングエラーで あるからです。スレッドの結果を回収しない場合は、 thunk中ですべての例外を捕捉し処理しなければなりません。

内部的に、この手続きは単にSchemeスレッドオブジェクトを割り当て初期化している だけです。POSIXスレッドはthread-start!が呼ばれるまで生成されません。

Function: thread-state thread

{gauche.threads} threadの状態を示す、newrunnablestoppedterminatedのいずれかのシンボルを返します。

Function: thread-name thread

[SRFI-18], [SRFI-21] {gauche.threads} threadのスロットnameの値を返します。

Function: thread-specific thread
Function: thread-specific-set! thread value

[SRFI-18], [SRFI-21] {gauche.threads} threadの指定したスレッドの値を取得/設定します。

Function: thread-start! thread

[SRFI-18], [SRFI-21] {gauche.threads} threadを開始します。threadがすでに開始されていればエラーになります。 threadを返します。

Function: thread-yield!

[SRFI-18], [SRFI-21] {gauche.threads} 呼び出しているスレッドの実行を中断し、他に待機中の実行可能なスレッドがあれば、 CPUにそれを処理させます。

Function: thread-sleep! timeout

[SRFI-18], [SRFI-21] {gauche.threads} 呼び出しているスレッドをtimeoutに指定した時間だけ中断します。 timeoutは絶対的な時間を表す<time>オブジェクト(時間参照)か、 この手続きが呼ばれた時刻からの相対的な秒数を表す実数でなければなりません。

指定された時間が経過すると、thread-sleep!は未定義値を返します。

timeoutが過去の時間を指していたら、thread-sleep!はすぐに戻ります。

Function: thread-stop! thread :optional timeout timeout-val

{gauche.threads} 対象となるスレッドthreadの実行を一時的に停止します。 停止されたスレッドはthread-cont!により実行を再開させられます。

停止リクエストは同期的に処理されます。すなわち、GaucheのVMは実行ループ中の 「安全な」場所で停止リクエストをチェックし、それがあれば自分自身を停止するように なっています。このことは、threadがシステムコールによりブロックされている場合、 それが戻ってくるまでスレッドがstopped状態にならないことを意味します。

デフォルトでは、thread-stop!は対象スレッドが停止状態になってから 戻ってきます。しかしそうなるまでにどれだけ時間がかかるかわからないので、 省略可能引数timeoutを与えて、タイムアウトすることができます。 timeout引数には#f(タイムアウトせず停止するまで待ち続ける)、 <time>オブジェクト(絶対的な時刻を指定)、もしくは 実数(現在からの秒数を指定)を与えられます。

thread-stop!の戻り値は、対象スレッドを停止させられたなら そのthreadオブジェクト、タイムアウトしたならtimeout-valです。 timeout-valが省略された場合は#fとみなされます。

対象スレッドが既に呼び出しスレッドの要求によって停止していた場合は、 直ちにthreadが返されます。

thread-stop!がタイムアウトしてthread-stop!から帰ってきた 場合でも、リクエストは有効なままになっており、未来のいつかthreadを 停止させます。呼び出し側は、いずれthread-stop!を再び呼び出して 停止を確認する必要があります。

対象スレッドが既に別のスレッドによって停止させられていた場合 (別のスレッドによって停止要求が出されて停止待ちに なっている状態も含みます)や、 対象スレッドがrunnableでもstoppedでもない状態の場合は エラーが報告されます。

Function: thread-cont! thread

{gauche.threads} thread-stop!で停止したthreadの実行を再開します。 threadが停止状態でなかったり、thread-cont!を呼び出したのとは 別のスレッドによって停止させられていた場合はエラーが報告されます。

停止要求を既に出していて、タイムアウトしていた場合、thread-cont!は その停止要求をキャンセルする役割を持ちます。

Function: thread-terminate! thread

[SRFI-18], [SRFI-21] {gauche.threads} 指定されたスレッドthreadを終了します。 threadは終了され、<terminated-thread-exception>のインスタンスが threadの結果例外のフィールドに格納されます。

threadが呼び出しているスレッドと同じ場合、この手続きは戻りません。 そうでなければ、この手続きは未定義値を返します。

threadには(dynamic-windでの’after’手続きのような)クリーンアップ手続きを 呼ぶチャンスがないので、この手続きは注意して使って下さい。 threadがクリティカルセクションにあるならば、一貫性のない状態を残すことに なります。 しかし、あるスレッドが一旦終了すると、そのスレッドが保持していたmutexは ’abandoned’(放棄された)状態になり、そのようなmutexをロックしようとするスレッドは ’abandoned mutex exception’を投げるので、その状況を知ることができます。 同期プリミティブ参照。

Function: thread-join! thread :optional timeout timeout-val

[SRFI-18], [SRFI-21] {gauche.threads} threadの終了、あるいはtimeoutが与えられていればtimeoutが それに達するのを待ちます。

Timeoutは絶対的な時間を表す<time>オブジェクト(時間参照)か、 この手続きが呼ばれた時刻からの相対的な時間を秒数で表した実数でなければなりません。 タイムアウトが指定されていない(デフォルト)は#fです。

threadが正常に終了したら、thread-join!threadの 結果フィールドに格納されている値を返します。 threadが異常終了したら、thread-join!threadの結果例外 フィールドに格納されている例外を投げます。それは <terminated-thread-exception><uncaught-exception>のどちらかです。

タイムアウトに達すると、timeout-valが与えられていればtimeout-valを返し、 与えられていなければ<join-timeout-exception>を投げます。

これらの例外の詳細についてはスレッド例外を参照してください。


Next: , Previous: , Up: スレッド   [Contents][Index]

9.33.3 同期プリミティブ

mutexと条件変数が低レベルの同期デバイスとして提供されます。 これらはsrfi-18とsrfi-21で定義されているので、サポートしているScheme実装間では ポータブルです。しかし多くの場合、次のような高レベルの同期ユーティリティを使った方が 良いです。

アトム

アトムは任意のSchemeオブジェクトをラップして、Javaのsynchronizedブロックに 似た形で同期アクセスを提供します。アトムについては本節で説明します。

MT-Queue

スレッドセーフなキュー(<mtqueue>)がdata.queueモジュールで 提供されます(キュー参照)。これは同期チャネルとして動作し、 生産者-消費者パターンを実装するのに適しています。

Mutex

Builtin Class: <mutex>

{gauche.threads} 同期のためのプリミティブなデバイスです。次の4つの状態のいずれかを持ちます: locked/owned、locked/not-owned、unlocked/abandoned、unlocked/not-abandoned。 Mutexは、それがunlocked(ロックされていない状態)であるときのみ、 (mutex-lock!により)ロックされます。 所有されている(owned) mutexは、そのmutexを所有しているスレッドを記憶しています。 通常、所有者となるスレッドはmutexをロックしたスレッドですが、 ロックしたのとは別のスレッドがmutexを所有するようにすることもできます。 ロックはmutex-unlock!によるか、所有するスレッドが終了すると解放されます。 前者の場合、mutexはunlocked/not-abandoned(ロックされておらず、放棄されていない状態) になります。 後者の場合、mutexはunlocked/abandoned(ロックされておらず、放棄された状態)になります。

Mutexは、以下の外部スロットを持ちます。

Instance Variable of <mutex>: name

Mutexの名前。

Instance Variable of <mutex>: state

Mutexの状態。これは読み取りのみ可能なスロットです。 下記のmutex-stateの説明を参照して下さい。

Instance Variable of <mutex>: specific

アプリケーションが任意のデータを保持することのできるスロットです。 例えば、アプリケーションはこの固有フィールドで’再帰的な’ mutexを 実装することができます。

Function: mutex? obj

[SRFI-18], [SRFI-21] {gauche.threads} objがmutexであれば#t、そうでなければ#fを返します。

Function: make-mutex :optional name

[SRFI-18], [SRFI-21] {gauche.threads} 新しいmutexオブジェクトを生成して返します。 生成時には、mutexの状態は、unlocked/not-abandoned(ロックされておらず、 放棄されていない状態)です。オプションで、このmutexに名前を付けることができます。

Function: mutex-name mutex

[SRFI-18], [SRFI-21] {gauche.threads} Mutexの名前を返します。

Function: mutex-specific mutex
Function: mutex-specific-set! mutex value

[SRFI-18], [SRFI-21] {gauche.threads} Mutexの固有の値を取得/セットできます。

Function: mutex-state mutex

[SRFI-18], [SRFI-21] {gauche.threads} mutexの状態を返します。状態は以下のうちの1つです。

あるスレッド

Mutexはlocked/owned(ロックされ所有されている)で、所有者は返されたスレッド。

シンボル not-owned

Mutexはlocked/not-owned(ロックされているが所有されていない)。

シンボル abandoned

Mutexはunlocked/abandoned(ロックされておらず、放棄されている)。

シンボル not-abandoned

Mutexはunlocked/not-abandoned(ロックされておらず、放棄されていない)。

Function: mutex-lock! mutex :optional timeout thread

[SRFI-18], [SRFI-21] {gauche.threads} mutexをロックします。mutexがunlocked/not-abandoned( ロックされておらず放棄されていない状態)なら、 この手続きはその状態を排他的なlocked(ロックされた状態)に変更します。 デフォルトでは、mutexはlocked/owned(ロックされ、所有された状態)になり、 所有者は呼び出したスレッドです。 他の所有しているスレッドを、引数threadを与えることもできます。 引数thread#fが与えられると、mutexはlocked/not-owned (ロックされ所有されていない状態)になります。

mutexがunlocked/abandoned(ロックされておらず放棄された状態)ならば、それはつまり、 他の何らかのスレッドがそのロックを解放せずに終了した場合、 この手続きはmutexの状態を変更した後に、’abandoned mutex exception’ (スレッド例外参照)を通知します。

mutexがlocked(ロックされた状態)で、timeoutが省略されるか#fならば、 この手続きはmutexのロックが解放されるまでブロックします。 timeoutが指定されている場合は、ロックが獲得できなかったケースでは 指定された時間に達した時にmutex-lock!は戻ります。 timeoutには、絶対的な時間(<time>オブジェクト、時間参照)か、 相対的な時間を(実数で)指定できます。

mutexのロックが成功するとmutex-lock!#tを返し、 タイムアウトに達すると#fが返ります。

mutexそれ自身は’再帰的なロック’の機能は実装していません。 つまり、mutexをロックしたスレッドが再度mutexをロックしようと すると、そのスレッドはブロックします。しかし、このmutexに 基づいて再帰的なロックのセマンティクスを実装することは難しくありません。 次の例は、SRFI-18のドキュメントから引用したものです。

(define (mutex-lock-recursively! mutex)
  (if (eq? (mutex-state mutex) (current-thread))
      (let ((n (mutex-specific mutex)))
        (mutex-specific-set! mutex (+ n 1)))
      (begin
        (mutex-lock! mutex)
        (mutex-specific-set! mutex 0))))

(define (mutex-unlock-recursively! mutex)
  (let ((n (mutex-specific mutex)))
    (if (= n 0)
        (mutex-unlock! mutex)
        (mutex-specific-set! mutex (- n 1)))))
Function: mutex-unlock! mutex :optional condition-variable timeout

[SRFI-18], [SRFI-21] {gauche.threads} mutexをアンロックします。mutexの状態は、unlocked/not-abandoned (ロックされておらず、放棄されていない状態)となります。 呼び出しているスレッドにより所有されていないmutexをアンロックすることは 許されています。

オプショナル引数のconditional-variableが与えられている場合、 mutex-unlock!は“条件変数待機”の動作も行います(例えば、POSIXスレッドの pthread_cond_wait)。 現在のスレッドはmutexをアンロックし、 condition-variableの待ち状態に入る動作をアトミックに行います。 スレッドは、他のスレッドがcondition-variableにシグナルを通知するか (下記のcondition-variable-signal!condition-variable-broadcast!を 見て下さい)、 timeoutが与えられていてそれに達すると、ブロックが解除されます。 引数timeoutは、絶対的な時間を表す<time>オブジェクト(時間参照)、 相対的な時間を秒数で表す実数、タイムアウトしないことを表す#fのいずれかです。 ブロックが解除された時に、必ずしも条件が満たされているとは限らないので、 次に挙げる例(SRFI-18のドキュメントより引用)のように、 呼び出したスレッドはmutexのロックを再獲得して条件を検査するべきです。

(let loop ()
  (mutex-lock! m)
  (if (condition-is-true?)
      (begin
        (do-something-when-condition-is-true)
        (mutex-unlock! m))
      (begin
        (mutex-unlock! m cv)
        (loop))))

mutex-unlock!の戻り値は、タイムアウトした場合に#f、 それ以外の場合は#tとなります。

Function: mutex-locker mutex
Function: mutex-unlocker mutex

{gauche.threads} それぞれ(lambda () (mutex-lock! mutex))(lambda () (mutex-unlock! mutex)) を返します。 これらのクロージャは、各mutexにつきひとつづつしか作られないため、 タイトなループの中ではこれらの形のリテラルなラムダ式を使うよりも軽量です。

Function: with-locking-mutex mutex thunk

{gauche.threads} mutexをロックしてthunkを呼びます。次のように実装されています。

(define (with-locking-mutex mutex thunk)
  (dynamic-wind
   (mutex-locker mutex)
   thunk
   (mutex-unlocker mutex)))

条件変数

Builtin Class: <condition-variable>

{gauche.threads} 条件変数は、ある条件が真になるのを待っているスレッドの集合を保持します。 あるスレッドがその条件を変更する時、condition-variable-signal!あるいは condition-variable-broadcast!が呼ばれ、それは1つ以上の待機中の スレッドのブロックを解除するため、それらのスレッドは条件が満足するかどうか 検査できます。

条件変数オブジェクトは以下のスロットを持ちます。

Instance Variable of <condition-variable>: name

条件変数の名前。

Instance Variable of <condition-variable>: specific

アプリケーションが任意のデータを保持できるスロット。

SRFI-18は、pthreadのpthread_cond_waitに相当する手続きを 持たないことに注意してください。条件変数を待つのは、 mutex-unlock!の省略可能引数に条件変数を渡し、 その後mutexを再びmutex-lock!で得ることで行います。 この設計は柔軟性のためです。詳しくはSRFI-18を参照して下さい。

このような、pthreadで条件変数を使う定石は:

while (some_condition != TRUE) {
  pthread_cond_wait(condition_variable, mutex);
}

SRFI-18では次のようなコードになります。

(let loop ()
  (unless some-condition
    (mutex-unlock! mutex condition-variable)
    (mutex-lock! mutex)
    (loop)))
Function: condition-variable? obj

[SRFI-18], [SRFI-21] {gauche.threads} objが条件変数なら#t、そうでなければ#fを返します。

Function: make-condition-variable :optional name

[SRFI-18], [SRFI-21] {gauche.threads} 新しい条件変数を返します。オプショナル引数nameで その名前を与えることができます。

Function: condition-variable-name cv

[SRFI-18], [SRFI-21] {gauche.threads} 条件変数の名前を返します。

Function: condition-variable-specific cv
Function: condition-variable-specific-set! cv value

[SRFI-18]、[SRFI-21] {gauche.threads} 条件変数の固有の値を取得/セットします。

Function: condition-variable-signal! cv

[SRFI-18]、[SRFI-21] {gauche.threads} cvで待機しているスレッドがある場合は、それらのうちの1つがスケジューラに より選択され、実行可能にされます。

Function: condition-variable-broadcast! cv

[SRFI-18]、[SRFI-21] {gauche.threads} cvで待機している全てのスレッドのブロックを解除します。

アトム

アトムはオブジェクトの集まりをスレッドセーフに簡単に扱うことができるラッパーです。 それぞれの構造に対応するスレッドセーフ版を作るかわりに、 既にあるいくつかのデータをまとめて包んでスレッドセーフにできます。

Function: atom val …

{gauche.threads} val …を初期値に持つアトムオブジェクトを作って返します。

Function: atom? obj

{gauche.threads} objがアトムなら#tを、そうでなければ#fを返します。

以下の手続きは、アトムの中身にアトミックにアクセスしたり、 それをアップデートするのに使えます。共通する省略可能引数として、 timeouttimeout-valを取ります。 これらのデフォルト値は#fです。手続きによっては一つ以上のtimeout-val を取ることがあります。 timeout引数がデフォルト値#fであれば、 以下の手続きはロックが得られるまでブロックします。

timeout引数はすぐにロックが得られなかった場合の動作を変更します。 timeout引数には、絶対的な時刻を指定する<time>オブジェクト (時間参照)か、相対的な時刻を秒数で指定する実数を渡すことができます。 指定された時刻が過ぎたら、以下の手続きはロックを獲得するのを諦めて、 timeout-valに指定された値を返します。 atomicatomic-update!は一つ以上の値を返し得るので、 一つ以上のtimeout-valを渡せます。

Function: atom-ref atom :optional index timeout timeout-val

{gauche.threads} アトムatomindex番目の値を返します。 timeouttimeout-valについては上記参照。

(define a (atom 'a 'b))

(atom-ref a 0) ⇒ a
(atom-ref a 1) ⇒ b
Function: atomic atom proc :optional timeout timeout-val timeout-val2 …

{gauche.threads} atomをロックして、procを現在のatomの値に適用します。 procatomの値の数だけの引数を取ることができなければなりません。

タイムアウトが起こらない限り、procが返した値がatomicの返り値となります。 timeouttimeout-valについては上記参照。

例えば次のref/countは、 ハッシュテーブルが参照された回数を数えるスレッドセーフな手続きです。

(define a (atom (make-hash-table 'eq?) (list 0)))

(define (ref/count a key)
  (atomic a
   (lambda (ht count-cell)
     (inc! (car count-cell))
     (hash-table-get h key))))
Function: atomic-update! atom proc :optional timeout timeout-val timeout-val2 …

{gauche.threads} atomをロックして、その値を引数としてprocを呼び、 procの戻り値をatomの新たな値とします。 procatomの値の数だけ引数を取らねばならず、 またatomの値の数以上の戻り値を返さねばなりません。 procatomの値の数以上の値を返した場合、 余分な値はatomを更新するのには使われませんが、 atomic-update!の結果には含まれます。

atomic-update!の戻り値は、タイムアウトが起こらなければprocの戻り値です。 timeouttimeout-valについては上記参照。

下はスレッドセーフなカウンタです。

(define a (atom 0))

(atomic-update! a (cut + 1 <>))

atomは古のLispでは「コンスセル(ペア)でないもの」を指していました。 当時、コンスセルは唯一の複合型であり、他の型もごくわずか(数値とシンボル)だったので、 セルでなものを指すことには意味がありました。

今でもatomはLispのチュートリアルで目にすることがありますが、 Schemeをはじめ現代のLispではたくさんのデータ型があり、 コンスセル以外をひとまとめにする用語を使う意味がほとんどありません。

Clojureはatomをスレッドセーフ(アトミック)な基本データに使ったので、 Gaucheもそれに倣うことにしました。

註: コンストラクタがmake-atomではなくatomなのは、 list/make-listvector/make-vectorstring/make-stringなどの慣習に従っています。 つまり、make-のない手続きは、各要素を可変長引数の形で取ります。


Previous: , Up: スレッド   [Contents][Index]

9.33.4 スレッド例外

例外のいくつかのタイプは、スレッド関連の手続きから投げられます。 これらの例外は、Gaucheの例外メカニズム(例外参照)により 扱われます。

Builtin Class: <thread-exception>

{gauche.threads} スレッド関連の例外の基底クラスです。<exception>クラスを継承しています。 スロットを1つ持っています。

Instance Variable of <thread-exception>: thread

この例外を投げたスレッド。

Builtin Class: <join-timeout-exception>

{gauche.threads} 待機していたスレッドが戻る前にタイムアウトに達した時にthread-join!によって 投げられる例外。<thread-exception>を継承しています。

Builtin Class: <abandoned-mutex-exception>

{gauche.threads} ロックされるmutexが、unlocked/abandoned(ロックされておらず、放棄された状態) であるときにmutex-lock!により投げられる例外。 <thread-exception>を継承しています。スロットを1つ持ちます。

Instance Variable of <abandoned-mutex-exception>: mutex

この例外の原因となったmutex。

Builtin Class: <terminated-thread-exception>

{gauche.threads} 待機していたスレッドが(thread-terminate!により)異常終了した 場合に(thread-join!により)投げられる例外。 <thread-exception>を継承し、スロットを1つ持ちます。

Instance Variable of <terminated-thread-exception>: terminator

この例外の原因となったスレッドを終了したスレッド。

Builtin Class: <uncaught-exception>

{gauche.threads} 待機していたスレッドが捕捉されない例外により終了された場合に thread-join!により投げられる例外。 <thread-exception>を継承し、スロットを1つ持ちます。

Instance Variable of <uncaught-exception>: reason

そのスレッドの終了の原因となった例外。

Function: join-timeout-exception? obj
Function: abandoned-mutex-exception? obj
Function: terminated-thread-exception? obj
Function: uncaught-exception? obj

[SRFI-18], [SRFI-21] {gauche.threads} これらの手続きは、objが特定のタイプの例外かどうかを検査します。 SRFI-18との互換性のために提供されています。

Function: uncaught-exception-reason exc

[SRFI-18], [SRFI-21] {gauche.threads} <uncaught-exception>オブジェクトのreasonスロットの値を 返します。 SRFI-18との互換性のために提供されています。


Previous: , Up: スレッド   [Contents][Index]