[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

9.23 gauche.threads - スレッド

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

Module: gauche.threads

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

動作中のGaucheプログラムの中でスレッドが有効かどうかをチェックするためには、 次の手続きを使います。

Function: gauche-thread-type

サポートされているスレッドのタイプを表すシンボルを返します。 現在のバージョンでは、POSIXスレッドの上に構築されるスレッドが 有効な場合はpthreadを返し、スレッドが有効でない場合は noneを返します。

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


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

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

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

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

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

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

プリエンプティブなスレッドには他にも難しい点があり (FairThreads参照)、 しばしばネイティブなプリエンプティブスレッドよりも より良くフィットする代替策があります。

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


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

9.23.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] 現在のスレッドを返します。

Function: thread? obj

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

Function: make-thread thunk &optional name

[SRFI-18]、[SRFI-21] 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

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

Function: thread-name thread

[SRFI-18]、[SRFI-21] threadのスロットnameの値を返します。

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

[SRFI-18]、[SRFI-21] threadの指定したスレッドの値を取得/設定します。

Function: thread-start! thread

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

Function: thread-yield!

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

Function: thread-sleep! timeout

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

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

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

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

Stops execution of the target thread temporarily. You can resume the execution of the thread by thread-cont!.

The stop request is handled synchronously; that is, Gauche VM only checks the request at the “safe” point of the VM and stops itself. It means if the thread is blocked by a system call, it won't become stopped state until the system call returns.

By default, thread-stop! returns after the target thread stops. Since it may take indefinitely, you can give optional timeout argument to specify timeout. The timeout argument can be #f, which means no timeout, or a <time> object that specifies an absolute point of time, or a real number specifying the number of seconds to wait.

The return value of thread-stop! is thread if it could successfully stop the target, or timeout-val if timeout reached. When timeout-val is omitted, #f is assumed.

If the target thread has already been stopped by the caller thread, this procedure returns immediately.

When thread-stop! is timed out, the request remains effective even after thread-stop! returns. That is, the target thread may stop at some point in future. The caller thread is expected to call thread-stop! again to complete the stop operation.

An error is signalled if the target thread has already been stopped by another thread (including the “pending” stop request issued by other threads), or the target thread is in neither runnable nor stopped state.

Function: thread-cont! thread

Resumes execution of thread which has been stopped by thread-stop!. An error is raised if thread is not in stopped state, or it is stopped by another thread.

If the caller thread has already requested to stop the target thread but timed out, calling thread-cont! cancels the request.

Function: thread-terminate! thread

[SRFI-18]、[SRFI-21] 指定されたスレッド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] 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>を投げます。

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


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

9.23.3 同期プリミティブ

Builtin Class: <mutex>

同期のための基本的デバイスです。次の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] objがmutexであれば#t、そうでなければ#fを返します。

Function: make-mutex &optional name

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

Function: mutex-name mutex

[SRFI-18]、[SRFI-21] Mutexの名前を返します。

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

[SRFI-18]、[SRFI-21] Mutexの固有の値を取得/セットできます。

Function: mutex-state mutex

[SRFI-18]、[SRFI-21] 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] 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] 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: with-locking-mutex mutex thunk

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

 
(define (with-locking-mutex mutex thunk)
  (dynamic-wind
   (lambda () (mutex-lock! mutex))
   thunk
   (lambda () (mutex-unlock! mutex))))
Builtin Class: <condition-variable>

条件変数は、ある条件が真になるのを待っているスレッドの集合を保持します。 あるスレッドがその条件を変更する時、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] objが条件変数なら#t、そうでなければ#fを返します。

Function: make-condition-variable &optional name

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

Function: condition-variable-name cv

[SRFI-18]、[SRFI-21] 条件変数の名前を返します。

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

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

Function: condition-variable-signal! cv

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

Function: condition-variable-broadcast! cv

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


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

9.23.4 スレッド例外

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

Builtin Class: <thread-exception>

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

Instance Variable of <thread-exception>: thread

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

Builtin Class: <join-timeout-exception>

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

Builtin Class: <abandoned-mutex-exception>

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

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

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

Builtin Class: <terminated-thread-exception>

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

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

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

Builtin Class: <uncaught-exception>

待機していたスレッドが捕捉されない例外により終了された場合に 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] これらの手続きは、objが特定のタイプの例外かどうかを検査します。 SRFI-18との互換性のために提供されています。

Function: uncaught-exception-reason exc

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


[ < ] [ > ]   [ << ] [ Up ] [ >> ]

This document was generated by Shiro Kawai on November, 22 2009 using texi2html 1.78.