属性は、デフォルトとは異なる動作を指定する手段です。pthread_create(3T) でスレッドを生成する場合または同期変数を初期化する場合は、属性オブジェクトを指定できます。通常は、デフォルトで間に合います。
属性はスレッド生成時にのみ指定されます。スレッドを使用中は変更できません。
属性オブジェクトはプログラマからは「不透明」なため、代入によって直接変更できません。各オブジェクト型を初期化、設定、または削除するための関数のセットが用意されています。
いったん初期化して設定した属性は、プロセス全体に適用されます。属性を使用するための望ましいやり方は、必要なすべての状態の指定をプログラム実行の初期の段階で一度に設定することです。そうすれば、必要に応じて適切な属性オブジェクトを参照できます。
属性オブジェクトを使用することには、主に次の 2 つの利点があります。
第 1 に、コードの移植性が高まります。
サポートされる属性は実装によって異なっていても、属性オブジェクトはインタフェースから隠されているので、スレッド実体を生成するための関数呼び出しを変更する必要はありません。
移植の対象となる実装が、現在の実装にない属性をサポートしている場合は、新しい属性を管理するために準備が必要です。ただし、属性オブジェクトは明確に定義された位置で一度だけ初期化すればよいので、この移植作業は難しくはありません。
第 2 に、アプリケーションでの状態指定が簡素化されます。
一例として、同じプロセス内にスレッドの集合がいくつか存在し、それぞれが別のサービスを提供するとともに独自の状態要件をもっているという状況を考えてみます。
アプリケーションの初期段階のどこかの時点で、1 つのスレッドの属性オブジェクトを集合ごとに初期化できます。以降のすべてのスレッド生成は、そのタイプのスレッドについて初期化された属性オブジェクトを参照します。初期化フェーズは単純で現地仕様化されているので、後で変更が必要になっても、すばやく確実に実行できます。
属性オブジェクトの取り扱いで注意を要するのは、プロセス終了時です。オブジェクトが初期化されるときにメモリーが割り当てられます。このメモリーをシステムに戻す必要があります。pthread 規格には、属性オブジェクトを削除する関数呼び出しが用意されています。
pthread_attr_init(3T) は、オブジェクトの属性をデフォルト値に初期化します。その記憶領域は、実行中にスレッドシステムによって割り当てられます。
プロトタイプ: int pthread_attr_init(pthread_attr_t *tattr); #include <pthread.h> pthread_attr_t tattr; int ret; /* 属性をデフォルト値に初期化する */ ret = pthread_attr_init(&tattr); |
表 3-1 に属性 (tattr) のデフォルト値を示します。
表 3-1 tattr のデフォルト属性値
属性 |
値 |
結果 |
---|---|---|
scope |
PTHREAD_SCOPE_PROCESS |
新しいスレッドは非結合 (LWP に固定的に結合されない) |
detachstate |
PTHREAD_CREATE_JOINABLE |
スレッドの終了後に終了状態とスレッドが保存される |
stackaddr |
NULL |
新しいスレッドはシステムによって割り当てられたスタックアドレスをもつ |
stacksize |
1M バイト |
新しいスレッドはシステムによって定義されたスタックの大きさをもつ |
priority |
|
新しいスレッドは親スレッドの優先順位を継承する |
inheritsched |
PTHREAD_INHERIT_SCHED |
新しいスレッドは親スレッドのスケジューリング優先順位を継承する |
schedpolicy |
SCHED_OTHER |
新しいスレッドは Solaris で定義された固定的な優先順位スケジューリングを使用する。スレッドは、優先順位の高いスレッドに取って代わられるまで、あるいはブロックするか実行権を明け渡すまで動作する |
正常終了時は 0 です。それ以外の戻り値は、エラーが発生したことを示します。以下の条件が検出されると、この関数は失敗し、対応する値を返します。
ENOMEM
メモリーが不足し、スレッド属性オブジェクトを初期化できないときに返されます。
pthread_attr_destroy(3T) は、初期化時に割り当てられた記憶領域を削除します。その属性オブジェクトは無効になります。
プロトタイプ: int pthread_attr_destroy(pthread_attr_t *tattr); #include <pthread.h> pthread_attr_t tattr; int ret; /* 属性を削除する */ ret = pthread_attr_destroy(&tattr); |
正常終了時は 0 です。それ以外の戻り値は、エラーが発生したことを示します。以下の条件が検出されると、この関数は失敗し、対応する値を返します。
スレッドを切り離された状態 (PTHREAD_CREATE_DETACHED) として生成すると、そのスレッドが終了するとすぐに、そのスレッド識別子とその他のリソースを再利用できます。呼び出したスレッドでスレッドの終了まで待ちたくない場合は、pthread_attr_setdetachstate(3T) を使用してください。
スレッドを切り離されていない状態 (PTHREAD_CREATE_JOINABLE) として生成すると、そのスレッドを待つものとみなされます。つまり、そのスレッドに対して pthread_join(3T) を実行するとみなされます。
スレッドが切り離された状態か切り離されていない状態で作成されたかに関係なく、すべてのスレッドが終了するまでプロセスは終了しません。「スレッド終了処理の完了」にある、main() から処理途中で戻ることによって生じるプロセスの終了の説明を参照して下さい。
プロトタイプ: int pthread_attr_setdetachstate(pthread_attr_t *tattr,int detachstate); #include <pthread.h> pthread_attr_t tattr; int ret; /* スレッド切り離し状態を設定する */ ret = pthread_attr_setdetachstate(&tattr,PTHREAD_CREATE_DETACHED); |
明示的な同期によって阻止されなければ、新たに生成される切り離されたスレッドは、そのスレッドの生成元が pthread_create() から復帰する前に終了でき、そのスレッド識別子は別の新しいスレッドに割り当てることができます。
切り離されていない (PTHREAD_CREATE_JOINABLE) スレッドについては、そのスレッドの終了後に他のスレッドが終了待ちを行うことがきわめて重要です。そうしないと、そのスレッドのリソースが新しいスレッドに解放されません。これは通常、メモリーリークを招くことになります。終了待ちを行うつもりがない場合は、スレッド作成時に切り離されたスレッドとして作成してください。
#include <pthread.h> pthread_attr_t tattr; pthread_t tid; void *start_routine; void arg int ret; /* デフォルト属性で初期化する */ ret = pthread_attr_init()(&tattr); ret = pthread_attr_setdetachstate()(&tattr,PTHREAD_CREATE_DETACHED); ret = pthread_create()(&tid, &tattr, start_routine, arg); |
正常終了時は 0 です。それ以外の戻り値は、エラーが発生したことを示します。以下の条件が検出されると、この関数は失敗し、対応する値を返します。
pthread_attr_getdetachstate(3T) は、スレッドの生成状態を取得します。これは「切り離された」または「切り離されていない」状態です。
プロトタイプ: int pthread_attr_getdetachstate(const pthread_attr_t *tattr, int *detachstate; #include <pthread.h> pthread_attr_t tattr; int detachstate; int ret; /* スレッドの切り離し状態を取得する */ ret = pthread_attr_getdetachstate (&tattr, &detachstate); |
正常終了時は 0 です。それ以外の戻り値は、エラーが発生したことを示します。以下の条件が検出されると、この関数は失敗し、対応する値を返します。
pthread_attr_setguardsize(3T) は、attr オブジェクトの guardsize (ガードサイズ) を設定します。
guardsize 引数は、スタックポインタのオーバーフローを防ぐためのものです。ガードとともにスレッドのスタックが作成されると、実装は、スタックのオーバーフローの終わりに、スタックポインタのスタックオーバーフローの緩衝域として、余分のメモリーを割り当てます。このバッファにアプリケーションがオーバーフローすると、スレッドに SIGSEGV シグナルが配信されるなどのエラーが発生します。
ガードサイズ属性をアプリケーションで使用する目的は、次の 2 つです。
オーバーフローを防止すると、システムリソースが無駄になるおそれがあります。多くのスレッドが作成されるアプリケーションは、そのスレッドがスタックをオーバーフローしないことがわかっている場合には、ガード領域をオフにすることで、システムリソースを節約できます。
スレッドがスタックに割り当てたデータ構造が大きい場合は、スタックオーバーフローを検出するために、大きなガード領域が必要になることがあります。
guardsize が 0 の場合は、attr を使って作成したスレッドにはガード領域が含まれません。guardsize が 0 よりも大きい場合は、少なくとも guardsize バイトのガード領域が、attr を使って作成した各スレッドに割り当てられます。デフォルトでは、スレッドは実装で定義された 1 バイト以上のガード領域を持ちます。
POSIX では、guardsize の値を、設定可能なシステム変数 PAGESIZE (sys/mman.h の「PAGESIZE」を参照) の倍数に切り上げるように、実装が認められています。実装が guardsize の値を PAGESIZE の倍数に切り上げる場合は、attr を指定して pthread_attr_getguardsize() を呼び出すと、guardsize には前回 pthread_attr_setguardsize() を呼び出したときに指定されたガードサイズが格納されます。
#include <pthread.h> int pthread_attr_setguardsize(pthread_attr_t *attr, size_t guardsize); |
以下の戻り値は、pthread_attr_setguardsize() が失敗したことを示します。
EINVAL
引数 attr が無効であるか、引数 guardsize が無効であるか、あるいは guardsize に無効な値が含まれています。
pthread_attr_getguardsize(3T) は、attr オブジェクトの guardsize を取得します。
POSIX では、guardsize の値を、設定可能なシステム変数 PAGESIZE (sys/mman.h の「PAGESIZE」を参照) の倍数に切り上げる実装が認められています。実装が guardsize の値を PAGESIZE の倍数に切り上げる場合は、attr を指定して pthread_attr_getguardsize() を呼び出すと、guardsize には前回 pthread_attr_setguardsize() を呼び出したときに指定されたガードサイズが使用されます。
#include <pthread.h> int pthread_attr_getguardsize(const pthread_attr_t *attr, size_t *guardsize); |
以下の戻り値は、pthread_attr_getguardsize() が失敗したことを示します。
EINVAL
引数 attr が無効であるか、引数 guardsize が無効であるか、あるいは guardsize に無効な値が含まれています。
pthread_attr_setscope(3T) は、結合スレッド (PTHREAD_SCOPE_SYSTEM) または非結合スレッド (PTHREAD_SCOPE_PROCESS) を生成します。
結合スレッドと非結合スレッドの両方とも、指定されたプロセス内でのみアクセスできます。
プロトタイプ: int pthread_attr_setscope(pthread_attr_t *tattr,int scope); #include <pthread.h> pthread_attr_t tattr; int ret; /* 結合スレッド */ ret = pthread_attr_setscope(&tattr, PTHREAD_SCOPE_SYSTEM); /* 非結合スレッド */ ret = pthread_attr_setscope(&tattr, PTHREAD_SCOPE_PROCESS); |
この例には、属性を初期化するもの、デフォルト属性を変更するもの、pthread を生成するものの 3 つの関数呼び出しがあります。
#include <pthread.h> pthread_attr_t attr; pthread_t tid; void start_routine; void arg; int ret; /* デフォルト属性による初期化 */ ret = pthread_attr_init (&tattr); /* 結合動作 */ ret = pthread_attr_setscope(&tattr, PTHREAD_SCOPE_SYSTEM); ret = pthread_create (&tid, &tattr, start_routine, arg); |
正常終了時は 0 です。それ以外の戻り値は、エラーが発生したことを示します。以下の条件が検出されると、この関数は失敗し、対応する値を返します。
pthread_attr_getscope(3T) は、スレッドのスコープを取得します。これはスレッドが結合するかしないかを示します。
プロトタイプ: int pthread_attr_getscope(pthread_attr_t *tattr, int *scope); #include <pthread.h> pthread_attr_t tattr; int scope; int ret; /* スレッドのスコープを取得する */ ret = pthread_attr_getscope(&tattr, &scope); |
正常終了時は 0 です。それ以外の戻り値は、エラーが発生したことを示します。以下の条件が検出されると、この関数は失敗し、対応する値を返します。
プロセス内の非結合スレッドは、同時にアクティブにする必要がある場合と、そうでない場合があります。デフォルトでは、スレッドの実装は、プロセスの処理を続行できる数のスレッドをアクティブにするように設定されています。デフォルト値のままにするとシステムリソースは節約できますが、最適の多重度ではない場合もあります。
pthread_setconcurrency(3T) を使用すると、 アプリケーションからスレッドの実装に、望ましい多重度 new_level を通知させることができます。この関数呼び出しの結果として実装が提供する実際の多重度は、定義されていません (Solaris スレッドについては、「thr_setconcurrency(3T)」参照)。
new_level が 0 の場合は、pthread_setconcurrency() が呼び出されなかったものとして、実装が任意の多重度を指定します。
アプリケーションは pthread_setconcurrency() を呼び出すときに、実装に目標多重度を通知します。実装はこの値を、要求ではなく参考として使用します。
#include <pthread.h> int pthread_setconcurrency(int new_level); |
以下の戻り値は、pthread_setconcurrency() が失敗したことを示します。
EINVAL
new_level で指定された値が負の値です。
EAGAIN
new_level で指定された値を使用するとシステムリソースの容量を超えます。
pthread_getconcurrency(3T) は、pthread_setconcurrency() への前回の呼び出しで設定された値を返します。pthread_setconcurrency() 関数が呼び出されたことがない場合は、0 を返します。0 は、実装が多重度を指定したことを示します (Solaris スレッドについては、「thr_getconcurrency(3T)」参照)。
#include <pthread.h> int pthread_getconcurrency(void); |
pthread_getconcurrency() は常に、pthread_setconcurrency() の前回の呼び出しで設定された値を返します。pthread_setconcurrency() が呼び出されたことがない場合は、pthread_getconcurrency() は 0 を返します。
pthread_attr_setschedpolicy(3T) は、スケジューリング方針を設定します。POSIX 規格の草稿ではスケジューリング方針の属性として、SCHED_FIFO (先入れ先出し)、SCHED_RR (ラウンドロビン)、SCHED_OTHER (実装で定義) を規定しています。
SCHED_FIFO
先入れ先出し。この方針でスケジュールしたスレッドは、優先順位の高いスレッドに割り込まれなければ、完了まで処理を進行します。スケジューリングの競合範囲がシステムであるスレッド (PTHREAD_SCOPE_SYSTEM) は、リアルタイム (RT) スケジューリングクラスに属し、呼び出しプロセスの実効ユーザ ID は 0 でなければいけません。スケジューリングの競合範囲がプロセス (PTHREAD_SCOPE_PROCESS) であるスレッド は、TS スケジューリングクラスに属します。
SCHED_RR
ラウンドロビン。この方針でスケジュールしたスレッドは、優先順位の高いスレッドに割り込まれなければ、システムによって定めれれた期間、処理を実行します。スケジューリングの競合範囲がシステムであるスレッド (PTHREAD_SCOPE_SYSTEM) は、リアルタイム (RT) スケジューリングクラスに属し、呼び出しプロセスの実効ユーザ ID は 0 でなければいけません。スケジューリングの競合範囲がプロセス (PTHREAD_SCOPE_PROCESS) であるスレッドの SCHED_RR は、TS スケジューリングクラスに属します。
SCHED_FIFO と SCHED_RR は POSIX では任意とされており、リアルタイム結合スレッドについてのみサポートされています
現在 pthread では、タイムシェアリングを示す Solaris の SCHED_OTHER のデフォルト値のみがサポートされています。スケジューリングの説明については、「スケジューリング」の節を参照してください。
プロトタイプ: int pthread_attr_setschedpolicy(pthread_attr_t *tattr, int policy); #include <pthread.h> pthread_attr_t tattr; int policy; int ret; /* スケジューリング方針を SCHED_OTHER に設定する */ ret = pthread_attr_setschedpolicy(&tattr, SCHED_OTHER); |
正常終了時は 0 です。それ以外の戻り値はエラーが発生したことを示します。以下のいずれかの条件が検出されると、この関数は失敗し、対応する値を返します。
pthread_attr_getschedpolicy(3T) は、スケジューリング方針を取得します。現在 pthread では、Solaris ベースの SCHED_OTHER デフォルト値のみがサポートされています。
プロトタイプ: int pthread_attr_getschedpolicy(pthread_attr_t *tattr, int *policy); #include <pthread.h> pthread_attr_t tattr; int policy; int ret; /* スレッドのスケジューリング方針を取得する */ ret = pthread_attr_getschedpolicy (&tattr, &policy); |
正常終了時は 0 です。それ以外の戻り値は、エラーが発生したことを示します。以下の条件が検出されると、この関数は失敗し、対応する値を返します。
pthread_attr_setinheritsched(3T) は、継承スケジューリング方針を設定します。
継承 (inherit) 値の PTHREAD_INHERIT_SCHED (デフォルト) の意味は、生成スレッドで定義されたスケジューリング方針を使用し、pthread_create() 呼び出しで定義されたスケジューリング方針は無視するということです。PTHREAD_EXPLICIT_SCHED を使用した場合は、pthread_create() 呼び出しでの属性が使用されます。
プロトタイプ: int pthread_attr_setinheritsched(pthread_attr_t *tattr, int inherit); #include <pthread.h> pthread_attr_t tattr; int inherit; int ret; /* 現在のスケジューリング方針を使用する */ ret = pthread_attr_setinheritsched(&tattr, PTHREAD_EXPLICIT_SCHED); |
正常終了時は 0 です。それ以外の戻り値は、エラーが発生したことを示します。以下のいずれかの条件が検出されると、この関数は失敗し、対応する値を戻します。
pthread_attr_getinheritsched(3T) は、pthread_attr_setinheritsched() によって設定された、スケジューリング方針を返します。
プロトタイプ: int pthread_attr_getinheritsched(pthread_attr_t *tattr, int *inherit); #include <pthread.h> pthread_attr_t tattr; int inherit; int ret; /* 生成スレッドのスケジューリング方針を取得する */ ret = pthread_attr_getinheritsched (&tattr, &inherit); |
正常終了時は 0 です。それ以外の戻り値は、エラーが発生したことを示します。以下の条件が検出されると、この関数は失敗し、対応する値を返します。
pthread_attr_setschedparam(3T) は、スケジューリングパラメタを設定します。
スケジューリングパラメタは param 構造体で定義します。ただし、サポートされるのは優先順位だけです。新たに生成されるスレッドは、この方針で動作します。
SCHED_FIFO
先入れ先出し。この方針でスケジュールしたスレッドは、優先順位の高いスレッドに割り込まれなければ、完了まで処理を進行します。スケジューリング競合範囲であるスレッド (PTHREAD_SCOPE_SYSTEM) は、リアルタイム (RT) スケジューリングクラスに属し、呼び出しプロセスの実効ユーザ ID は 0 でなければなりません。スケジューリング競合範囲がプロセス (PTHREAD_SCOPE_PROCESS) であるスレッド は、TS スケジューリングクラスに属します。
SCHED_RR
ラウンドロビン。この方針でスケジュールしたスレッドは、優先順位の高いスレッドに割り込まなければ、システムによって定められた期間、処理を実行します。スケジューリング競合範囲がシステムであるスレッド (PTHREAD_SCOPE_SYSTEM) は、リアルタイム (RT) スケジューリングクラスに属し、呼び出しプロセスの実効ユーザ ID は 0 でなければなりません。スケジューリング競合範囲がプロセス (PTHREAD_SCOPE_PROCESS) であるスレッドの SCHED_RR は、TS スケジューリングクラスに属します。
プロトタイプ: int pthread_attr_setschedparam(pthread_attr_t *tattr, const struct sched_param *param); #include <pthread.h> pthread_attr_t tattr; int newprio; sched_param param; newprio = 30; /* 優先順位を設定する。それ以外は変更なし */ param.sched_priority = newprio; /* 新しいスケジューリングパラメタを設定する */ ret = pthread_attr_setschedparam (&tattr, ¶m); |
正常終了時は 0 です。それ以外の戻り値は、エラーが発生したことを示します。以下の条件が検出されると、この関数は失敗し、対応する値を返します。
pthread の優先順位は、子スレッドを生成する前に優先順位属性を設定するか、親スレッドの優先順位を変更してまた戻す、のいずれかの方法で管理できます。
pthread_attr_getschedparam(3T) は、pthread_attr_setschedparam() によって設定されたスケジューリングパラメタを返します。
プロトタイプ: int pthread_attr_getschedparam(pthread_attr_t *tattr, const struct sched_param *param); #include <pthread.h> pthread_attr_t attr; struct sched_param param; int ret; /* 既存のスケジューリングパラメタを取得する */ ret = pthread_attr_getschedparam (&tattr, ¶m); |
正常終了時は 0 です。それ以外の戻り値は、エラーが発生したことを示します。以下の条件が検出されると、この関数は失敗し、対応する値を戻します。
スレッドを生成する前に優先順位属性を設定できます。子スレッドは、sched_param 構造体で指定した新しい優先順位で生成されます (この構造体には他のスケジューリング情報も含まれます)。
既存のパラメタを取得し、スレッドの優先順位を変更してから優先順位を再設定するという方法をお勧めします。
この方法の例を例 3-2 に示します。
#include <pthread.h> #include <sched.h> pthread_attr_t tattr; pthread_t tid; int ret; int newprio = 20; sched_param param; /* デフォルト属性で初期化する */ ret = pthread_attr_init (&tattr); /* 既存のスケジューリングパラメタを取得する */ ret = pthread_attr_getschedparam (&tattr, ¶m); /* 優先順位を設定する。それ以外は変更なし */ param.sched_priority = newprio; /* 新しいスケジューリングパラメタを設定する */ ret = pthread_attr_setschedparam (&tattr, ¶m); /* 指定した新しい優先順位を使用する */ ret = pthread_create (&tid, &tattr, func, arg); |
pthread_attr_setstacksize(3T) は、スレッドのスタックの大きさを設定します。
スタックサイズ属性は、システムが割り当てるスタックの大きさ (バイト数) を定義します。この大きさは、システムで定義された最小のスタックの大きさを下回ってはいけません。詳細は、「スタックについて」を参照してください。
プロトタイプ: int pthread_attr_setstacksize(pthread_attr_t *tattr, int size); #include <pthread.h> pthread_attr_t tattr; int size; int ret; size = (PTHREAD_STACK_MIN + 0x4000); /* 新しい大きさを設定する */ ret = pthread_attr_setstacksize(&tattr, size); |
上の例では、新しいスレッドが使用するスタックのバイト数が size に納められています。size の値が 0 ならば、デフォルトの大きさが使われます。ほとんどの場合、0 を指定すれば最善の結果が得られます。
PTHREAD_STACK_MIN は、スレッドを起動する上で必要なスタック空間の大きさです。しかし、アプリケーションコードを実行するのに必要なスレッドの関数が必要とするスタック空間の大きさは含まれていません。
正常終了時は 0 です。それ以外の戻り値は、エラーが発生したことを示します。以下の条件が検出されると、この関数は失敗し、対応する値を返します。
pthread_attr_getstacksize(3T) は、pthread_attr_setstacksize() によって設定された、スタックの大きさを返します。
プロトタイプ: int pthread_attr_getstacksize(pthread_attr_t *tattr, size_t *size); #include <pthread.h> pthread_attr_t tattr; int size; int ret; /* スタックの大きさを取得する */ ret = pthread_attr_getstacksize(&tattr, &size); |
正常終了時は 0 です。それ以外の戻り値は、エラーが発生したことを示します。以下の条件が検出されると、この関数は失敗し、対応する値を返します。
EINVAL
返された値が PTHREAD_STACK_MIN より小さいか、システムの制限を超えています。
通常、スレッドスタックはページ境界で始まり、指定した大きさは次のページ境界まで切り上げられます。アクセス権のないページがスタックの一番上に付加されることにより、ほとんどのスタックオーバーフローで、違反したスレッドに SIGSEGV シグナルが送られるようになります。呼び出し側によって割り当てられるスレッドスタックは、そのまま使われます。
スタックを指定するときは、スレッドを PTHREAD_CREATE_JOINABLE として生成してください。このスタックは、そのスレッドに対する pthread_join(3T) 呼び出しが戻るまで解放できません。これは、そのスレッドのスタックは、そのスレッドが終了するまで解放できないからです。スレッドが終了したかどうかを確実に知るには、pthread_join(3T) を使用してください。
通常、スレッド用にスタック空間を割り当てる必要はありません。スレッドライブラリが、各スレッドのスタックとして 1M バイトの仮想記憶を割り当てます。このときスワップ空間は確保されません。(このライブラリは、mmap() の MAP_NORESERVE オプションを使って割り当てを行います。)
スレッドライブラリで生成される各スレッドスタックには、レッドゾーンがあります。スレッドライブラリはレッドゾーンとして、スタックオーバーフローを補足するためのページをスタックの一番上に付加します。このページは無効で、アクセスされるとメモリーフォルトになります。レッドゾーンは、自動的に割り当てられるすべてのスタックに付加されます。これは、その大きさがアプリケーションで指定されたかデフォルトの大きさかに関係なく行われます。
実行時のスタック要件は一定ではないので、指定したスタックがライブラリの呼び出しと動的リンクに必要な実行時要件を確実に満足するようにしなければなりません。
スタックとスタックの大きさの一方または両方を指定するのが適正であることはほとんどありません。専門家であっても、適切な大きさを指定したかどうかを判断するのは困難です。これは、ABI 準拠のプログラムでもスタックの大きさを静的に判定できないからです。スタックの大きさは、プログラムが実行される、それぞれの実行環境に左右されます。
スレッドスタックの大きさを指定するときは、呼び出される関数に必要な割り当てを計算してください。これには、呼び出し手続きで必要とされる量、局所変数、情報構造体が含まれます。
デフォルトスタックと少し違うスタックが必要になることがあります。たとえば、スレッドで 1M バイトを超えるスタック空間が必要になる場合です。また、少し分かりにくいケースですが、デフォルトスタックが大きすぎる場合もあります。何千ものスレッドを生成するとすれば、デフォルトスタックでは合計サイズが数 G バイトにもなるため、仮想メモリが足りず、それだけのスタック空間を扱えないかもしれないからです。
スタックの大きさの上限は明らかであることが多いのですが、下限はどうでしょうか。スタックにプッシュされるスタックフレームを、その局所変数などを含めて、すべて扱えるだけのスタック空間が必要です。
マクロ PTHREAD_STACK_MIN を呼び出すと、スタックの大きさの絶対最小値が得られます。このマクロは、NULL 手続きを実行するスレッドに必要なスタック空間の大きさを戻します。実用的なスレッドに必要なスタック空間はもっと大きいので、スタックサイズを小さくするときは十分注意してください。
#include <pthread.h> pthread_attr_t tattr; pthread_t tid; int ret; int size = PTHREAD_STACK_MIN + 0x4000; /* デフォルト属性で初期化する */ ret = pthread_attr_init(&tattr); /* スタックの大きさも設定する */ ret = pthread_attr_setstacksize(&tattr, size); /* tattr に大きさのみを指定する */ ret = pthread_create(&tid, &tattr, start_routine, arg); |
独自のスタックを割り当てるときは、その終わりにレッドゾーンを付加するために必ず mprotect(2) を呼び出してください。
pthread_attr_setstackaddr(3T) は、スレッドスタックのアドレスを設定します。
stackaddr 属性は、スレッドのスタックのベースを定義するものです。これを NULL 以外の値に設定すると (NULL がデフォルト)、そのスタックはそのアドレスで初期化されます。
プロトタイプ: int pthread_attr_setstackaddr(pthread_attr_t *tattr,void *stackaddr); #include <pthread.h> pthread_attr_t tattr; void *base; int ret; base = (void *) malloc(PTHREAD_STACK_MIN + 0x4000); /* 新しいアドレスを設定する */ ret = pthread_attr_setstackaddr(&tattr, base); |
前の例では、新しいスレッドが使用するスタックのアドレスが base に格納されます。base の値が NULL ならば、pthread_create(3T) によって新しいスレッドに少なくとも PTHREAD_STACK_MIN バイトのスタックが割り当てられます。
正常終了時は 0 です。それ以外の戻り値は、エラーが発生したことを示します。以下の条件が検出されると、この関数は失敗し、対応する値を戻します。
次の例は、独自のスタックアドレスを指定してスレッドを生成する方法を示します。
#include <pthread.h> pthread_attr_t tattr; pthread_t tid; int ret; void *stackbase; stackbase = (void *) malloc(size); /* デフォルト属性で初期化する */ ret = pthread_attr_init(&tattr); /* 属性に基底アドレスを設定する */ ret = pthread_attr_setstackaddr(&tattr, stackbase); /* 属性 tattr に大きさのみを指定する */ ret = pthread_create(&tid, &tattr, func, arg); |
次の例は、独自のスタックアドレスと独自のスタックの大きさを指定してスレッドを生成する方法を示しています。
#include <pthread.h> pthread_attr_t tattr; pthread_t tid; int ret; void *stackbase; int size = PTHREAD_STACK_MIN + 0x4000; stackbase = (void *) malloc(size); /* デフォルト属性で初期化する */ ret = pthread_attr_init(&tattr); /* スタックの大きさも設定する */ ret = pthread_attr_setstacksize(&tattr, size); /* 属性に基底アドレスを設定する */ ret = pthread_attr_setstackaddr(&tattr, stackbase); /* アドレスと大きさを指定する */ ret = pthread_create(&tid, &tattr, func, arg); |
pthread_attr_getstackaddr(3T) は、pthread_attr_setstackaddr() によって設定された、スレッドスタックのアドレスを返します。
プロトタイプ: int pthread_attr_getstackaddr(pthread_attr_t *tattr,void **stackaddr); #include <pthread.h> pthread_attr_t tattr; void *base; int ret; /* 新しいアドレスを取得する */ ret = pthread_attr_getstackaddr (&tattr, &base); |
正常終了時は 0 です。それ以外の戻り値は、エラーが発生したことを示します。以下の条件が検出されると、この関数は失敗し、対応する値を戻します。