マルチスレッドのプログラミング

LWP とスケジューリングクラス

第 1 章「マルチスレッドの基礎」の「スケジューリング」の節で説明しているように、Solaris の pthread 実装でサポートしているスケジューリング方針は SCHED_OTHER だけです。それ以外は POSIX のオプションです。

POSIX の SCHED_FIFO 方針と SCHED_RR 方針は、Solaris 標準の機構を使って複製またはエミュレートできます。この節では、これらのスケジューリング機構について説明します。

Solaris のカーネルには、プロセスのスケジューリングに関する 3 つのクラスがあります。最も優先順位が高いスケジューリングクラスは、リアルタイム (RT) クラスです。その次はシステムクラスで、ユーザプロセスには適用されません。最も低いのはタイムシェア (TS) クラスで、デフォルトのスケジューリングクラスです。

スケジューリングクラスは、LWP ごとに維持管理されます。プロセスが生成されると、そのプロセスの初期 LWP は、親プロセスのスケジューリングクラスと作成元の LWP の優先順位を継承します。その後に、非結合スレッドを実行させるために生成される LWP も、このスケジューリングクラスと優先順位を継承します。

プロセス内のすべての非結合スレッドは、同じスケジューリングクラスと優先順位が与えられます。各スケジューリングクラスは、そのクラスに設定可能な優先順位に従って、スケジューリング対象の LWP の優先順位を、全体のディスパッチ優先順位に対応付けます。

結合スレッドは、結合している LWP と同じスケジューリングクラスと優先順位をもちます。あるプロセス内の各結合スレッドは、カーネルから参照可能な固有のスケジューリングクラスと優先順位を持っています。結合スレッドは、システム内のすべての LWP との関係の中でスケジューリングされます。

スレッドの優先順位は、LWP リソースへのアクセスを調整します。デフォルトでは、LWP はタイムシェアクラスです。計算量の多いマルチスレッドの場合、スレッドの優先順位はあまり役立ちません。MT ライブラリを使って多くの同期を行うマルチスレッドアプリケーションでは、スレッドの優先順位がより意味をもちます。

スケジューリングクラスは、システムコール priocntl(2) で設定します。最初の 2 つの引数で、この設定の適用範囲を呼び出し側の LWP に限定したり、1 つ以上のプロセスのすべての LWP にしたりすることが可能です。3 番目の引数はコマンドで、次のいずれか 1 つを指定できます。

priocntl() は結合スレッドにだけ使用します。非結合スレッドの優先順位を変更する場合は、pthread_setprio(3T) を使用してください。

タイムシェアスケジューリング

タイムシェアスケジューリングでは、このスケジューリングの LWP に処理リソースが公平に配分されます。カーネルのそれ以外の部分は、ユーザに対する応答時間に悪影響を与えないようにプロセッサを短時間ずつ使用します。

システムコール priocntl(2) は、1 つ以上のプロセスの nice() レベルを設定します。priocntl() による nice() レベルの変更は、そのプロセス内のタイムシェアクラスのすべての LWP に適用されます。nice() レベルの範囲は通常は 0〜+20 で、スーパーユーザ特権をもつプロセスの場合は -20〜+20 です。この値が小さいほど優先順位が高くなります。

タイムシェアクラスの LWP をディスパッチする優先順位は、LWP のその時点での CPU 使用率と nice() レベルに基づいて計算されます。タイムシェアスケジューラにとって、nice() レベルは、LWP 間の相対的な優先順位を表します。

LWP の nice() レベルが大きいほど、その LWP に配分される CPU 時間は少なくなりますが 0 になることはありません。多くの CPU 時間をすでに消費している LWP は、CPU 時間をほとんど (あるいは、まったく) 消費していない LWP よりも優先順位が下げられます。

リアルタイムスケジューリング

リアルタイム (RT) クラスは、プロセス全体またはプロセス内の 1 つ以上の LWP に適用できます。ただし、スーパーユーザ特権が必要です。

タイムシェアクラスの nice(2) レベルとは異なり、リアルタイムクラスを指定された LWP には、個々の LWP 単位または複数の LWP 単位で優先順位を設定できます。priocntl(2) システムコールで、プロセス内のリアルタイムクラスのすべての LWP の属性を変更できます。

スケジューラは、最も高い優先順位を持つリアルタイムクラスの LWP をディスパッチします。優先順位の高い LWP が実行可能状態になると、それよりも優先順位の低い LWP は、実行リソースを横取りされます。実行リソースを横取りされた LWP は、そのレベルの待ち行列の先頭に置かれます。

リアルタイムクラスの LWP は、実行リソースが横取りされたり、一時停止したり、リアルタイム優先順位が変更されたりしない限り、プロセッサの制御を保持し続けます。リアルタイムクラスの LWP には、タイムシェアクラスのプロセスよりも絶対的に高い優先順位が与えられます。

新しく生成された LWP は、親プロセスまたは親 LWP のスケジューリングクラスを継承します。リアルタイムクラスの LWP は、親のタイムスライス (リソース割り当て時間) を有限または無限指定に関係なく継承します。

有限タイムスライスを指定された LWP は、処理が終了するか、入出力イベント待ちなどによってブロックされるか、より優先順位の高い実行可能なリアルタイムプロセスによって実行リソースを横取りされるか、またはタイムスライスが満了するまで実行を続けます。

無限タイムスライスを指定された LWP が実行を停止するのは、LWP が終了するか、ブロックされるか、または実行リソースが横取りされたときだけです。

LWP のスケジューリングとスレッドの結合

スレッドライブラリは、非結合スレッドを実行するための、実行リソース内の LWP 数を自動的に調整します。これには次の目的があります。

タイムスライスが適用されるのは LWP であって、スレッドではありません。つまり、LWP が 1 つしか存在しなければ、プロセス内でタイムスライスは行われません。その LWP 上のスレッドは、スレッド間同期機構でブロックされるか、実行リソースを横取りされるか、または終了するまで実行を続けます。

スレッドに対する優先順位は、pthread_setprio(3T) で設定できます。優先順位の低い非結合スレッドは、それよりも優先順位の高い非結合スレッドが実行可能になっていないときだけ LWP に割り当てられます。ただし、結合スレッドは自分専用の LWP をもつので、LWP を争奪することはありません。なお、pthread_setprio() で設定されるスレッド優先順位は、CPU に対してではなく LWP に対するスレッドのアクセスを調整します。

スケジューリングをきめ細かく制御する必要がある場合は、スレッドを LWP に結合します。多数の非結合スレッドが 1 つの LWP を争奪するような状況では、きめ細かい制御を実現できないからです。

特に、優先順位の低い非結合スレッドが優先順位の高い LWP 上にあり CPU 上で実行されていて、優先順位の低い LWP に割り当てられた優先順位の高い非結合スレッドが実行されていないことがあります。このようにスレッドの優先順位は、CPU へのアクセスについての 1 つのヒントにすぎません。

リアルタイムスレッドは、外部からの入力に対して迅速な応答が必要なときに使用します。たとえば、マウスの動きを追跡するスレッドは、マウスボタンのクリックにただちに反応しなければなりません。そのスレッドを LWP に結合すれば、必要なときにいつでも LWP を使用できるようになります。その LWP をリアルタイムスケジューリングクラスに割り当てれば、マウスボタンのクリックに迅速に反応するようにスケジューリングされます。

SIGWAITING − 待ち状態のスレッドのための LWP の生成

スレッドライブラリは通常、プログラムを実行するのに十分な数の LWP が実行リソース内に存在することを保証します。

プロセス内のすべての LWP が無期限の待ち状態でブロックされる (たとえば、端末またはネットワークからの読み取りがブロックされる) と、オペレーティング環境は SIGWAITING というシグナル (新たに導入されたシグナル) をプロセスに送ります。このシグナルはスレッドライブラリで処理されます。このとき、実行待ちのスレッドがプロセス内にあれば新しい LWP を生成し、適当な待ち状態のスレッドを選択して、新しい LWP に割り当てて実行します。

SIGWAITING 機構は、複数のスレッドが計算を目的としていて、もう 1 つ別のスレッドが実行可能になった場合に新しい LWP が生成されるかどうかを保証していません。計算を目的とするスレッドは、LWP の不足のために複数の実行可能なスレッドが動作するのを妨げることがあります。

thr_setconcurrency(3THR) を呼び出すことによって、これを防ぐことができます。POSIX スレッドで thr_setconcurrency() を使用すると POSIX 準拠ではなくなりますが、計算量の多い状況で非結合スレッド用の LWP が不足するのを回避するには、この使い方が望ましいでしょう。(POSIX に完全に準拠し LWP の不足も回避する唯一の方法は、PTHREAD_SCOPE_SYSTEM 結合スレッドのみを生成することです。)

thr_setconcurrency(3THR) 関数の使用方法の詳細は、「スレッドの並行度 (Solaris スレッドの場合のみ)」を参照してください。

Solaris スレッドでは、thr_create(3THR) 呼び出しで THR_NEW_LWP を使って、別の LWP を生成するという方法もあります。

LWP の存在時間

アクティブなスレッドが少なくなると、実行リソース内の一部の LWP は必要なくなります。LWP の数がアクティブなスレッドの数より多いとき、スレッドライブラリは不要な LWP を削除します。スレッドライブラリは LWP の存在時間を監視し、長い間 (現行では 5 分間) 使用されていない LWP は削除します。