同期スケジューリング

一定のスレッドを他より速く実行する必要があるという制約 (特に、 時間の制約) がある場合、プログラマーは、スレッドの実行スケジューリングを制御することができます。

mutex などの同期オブジェクトは、 高優先順位のスレッドでさえブロックしてしまう場合があります。 時には、優先順位の逆転 と呼ばれる望ましくない振る舞いが起こる場合もあります。 スレッド・ライブラリーは、 優先順位の逆転を避けるための mutex プロトコル 機能を提供します。

同期スケジューリングは、スレッド実行スケジューリング (特に優先順位) が mutex を保持することによって、 どのように変更されるかを定義します。 これにより、カスタム定義の振る舞いが可能になり、 優先順位の逆転を避けることができます。 複合ロック方式を使用するときは、これが便利です。 スレッド・ライブラリーの一部のインプリメンテーションには、同期スケジューリング機能がありません。

優先順位の逆転

優先順位の逆転は、低優先順位のスレッドが mutex を保持して、 高優先順位のスレッドをブロックしているときに起こります。 mutex オーナーは、優先順位が低いので、 無限に mutex 保持し続ける可能性があります。 その結果、スレッドの期限を守ることができなくなります。

次の例は、一般的な優先順位の逆転を示しています。 この例では、ユニプロセッサー・システムの場合が考慮されます。 優先順位の逆転は、 マルチプロセッサー・システムでも同じようにして起こります。

この例では、mutex M は、ある共通データの保護のために使用されています。 スレッド A は、優先順位レベル 100 を持ち、非常に頻繁にスケジュールされます。 スレッド B は、優先順位レベル 20 を持つバックグラウンド・スレッドです。 プロセス内のその他のスレッドの優先順位レベルは 60 前後です。 スレッド A からのコード・フラグメントは、次のようになります。

pthread_mutex_lock(&M);             /* 1 */
...
pthread_mutex_unlock(&M);
スレッド B からのコード・フラグメントは、次のようになります。

pthread_mutex_lock(&M);          /* 2 */
...
fprintf(...);                    /* 3 */
...
pthread_mutex_unlock(&M);

次の実行順序を見てください。 スレッド B がスケジュールされ、行 2 を実行します。 行 3 の実行時に、スレッド A はスレッド B よりも優先使用されます。 スレッド A は行 1 を実行し、mutex M がスレッド B によって保持されたために、 ブロックされます。したがって、プロセス内の他のスレッドがスケジュールされます。 スレッド B は優先順位が非常に低いので、 長期間にわたってスケジュールを変更されない可能性があり、スレッド A が非常に高い優先順位を持つにもかかわらずスレッド A をブロックし続けます。

mutex プロトコル

優先順位の逆転を避けるために、 スレッド・ライブラリーによって次の mutex プロトコルが提供されています。

優先順位継承プロトコル
基本優先順位継承プロトコル と呼ばれる場合があります。 優先順位継承プロトコルでは、mutex 保持者が最高優先順位のブロックされたスレッドの優先順位を受け継ぎます。 あるスレッドがこのプロトコルを使用し、mutex をロックしようとしてブロックされている場合、mutex オーナーは、 一時的にブロックされているスレッドの優先順位がオーナーの優先順位より高ければ、 その優先順位を受け継ぎます。 mutex がアンロックされると、元の優先順位に戻ります。
優先順位保護プロトコル
優先順位上限プロトコル・エミュレーション と呼ばれる場合があります。 優先順位保護プロトコルでは、 それぞれの mutex に優先順位の上限 があります。 これは、優先順位の有効範囲内の優先順位です。 スレッドは、mutex を所有すると、mutex の優先順位上限がスレッドの優先順位より高い場合、 一時的にその上限を受け継ぎます。 mutex がアンロックされると、元の優先順位に戻ります。 優先順位上限の値は、mutex をロックする可能性のある、 すべてのスレッドの優先順位の最高値でなければなりません。 そうでなければ、優先順位の逆転またはデッドロックさえも発生する可能性があり、 プロトコルが非効率になります。

両方のプロトコルとも、特定の mutex を保持しているスレッドの優先順位を上げるので、 期限を確実化できるようになります。 さらに、正しく使用すると、mutex プロトコルが相互デッドロックを防ぐことができます。 mutex プロトコルは、個別に mutex に割り当てられます。

mutex プロトコルの選択

mutex プロトコルの選択は、mutex 作成時に属性を設定することによって行います。 mutex プロトコルは、プロトコル属性によって制御されます。 この属性は、pthread_mutexattr_getprotocol サブルーチンと pthread_mutexattr_setprotocol サブルーチンを使用して、mutex 属性オブジェクトに設定することができます。 プロトコル属性は、 次に示す値の中の 1 つです。
説明
PTHREAD_PRIO_DEFAULT 値なし
PTHREAD_PRIO_NONE プロトコルがないことを示します。
PTHREAD_PRIO_INHERIT 優先順位継承プロトコルを示します。
PTHREAD_PRIO_PROTECT 優先順位保護プロトコルを示します。
注: PTHREAD_PRIO_DEFAULT の振る舞いは、PTHREAD_PRIO_INHERIT 属性の場合と同様です。mutex のロックに関連して、ユーザーがロックされており、そのユーザーが所有者より高い優先順位を持っている場合、デフォルトの属性で動作するスレッドは一時的に mutex 所有者の優先順位を高くします。従って、属性構造体の可能な優先順位には 4 つの値があるにもかかわらず、発生する可能性のある振る舞いは 3 つのみとなります。

優先順位保護プロトコルは、1 つの追加属性、prioceiling 属性を使用します。 この属性には、mutex の優先順位上限が入っています。 prioceiling 属性は、mutex 属性オブジェクトで、pthread_mutexattr_getprioceiling および pthread_mutexattr_setprioceiling サブルーチンを使用して制御できます。

mutex の prioceiling 属性は、pthread_mutex_getprioceiling サブルーチン および pthread_mutex_setprioceiling サブルーチンを使用して動的に制御することもできます。 mutex の優先順位上限を動的に変更するとき、mutex はライブラリーによってロックされることに注意してください。 mutex は、pthread_mutex_setprioceiling サブルーチンのコール側のスレッドが、 デッドロック防止のために保持すべきではありません。 mutex の優先順位上限の動的設定は、 スレッドの優先順位を上げるときに有用である場合があります。

mutex プロトコルのインプリメンテーションは、オプショナルです。 それぞれのプロトコルは POSIX オプションです。

継承または保護

プロトコルは両方とも類似していて、 結果的に mutex を保持しているスレッドの優先順位をプロモートします。 両方のプロトコルを使用できる場合は、プログラマーがプロトコルを選択する必要があります。 この選択は、mutex をロックするスレッドの優先順位が、mutex を作成するプログラマーに使用可能かどうかによって決まります。 一般に、ライブラリーによって定義されてアプリケーション・スレッドによって使用される mutex は継承プロトコルを使用するのに対して、 アプリケーション・プログラム内で作成された mutex は保護プロトコルを使用します。

パフォーマンスが重要なプログラムでは、パフォーマンスの考慮も選択に影響する場合があります。 ほとんどのインプリメンテーションでは、 (特に AIX® では) スレッドの優先順位を変更するとシステム・コールを行うことになります。 したがって、2 つの mutex プロトコルは、生成するシステム・コールの量が異なります。
  • 継承プロトコルを使用すると、mutex をロックしようとしてスレッドがブロックされるたびに、 システム・コールが行われます。
  • 保護プロトコルを使用した場合、mutex がスレッドによってロックされるたびに必ず 1 回のシステム・コールが行われます。

ほとんどのパフォーマンスが重要なプログラムでは、mutex は競合の少ないオブジェクトなので、 継承プロトコルを選択すべきです。 mutex は長時間にわたって保持されるわけではないので、 スレッドをロックしようとしたときにブロックされることはまずありません。