📝

スレッドメモ

2024/07/23に公開

スレッドの開始

int pthread_create(pthread_t *thread, const pthread_attr_t *attr,
void *(*start)(void *), void *arg);

thread: pthread_t(スレッドID)へのポインタ
attr: pthread_attr_t(スレッド属性設定)へのポインタ
start: スレッドの実行はstartに指定された関数をコールするところから始まる。
arg: startの引数。start(arg)から始まる。

スレッドの終了

スレッドの終了パターンは4つ

  • スレッド関数がリターンする
  • スレッドがpthread_exit()を実行する
  • pthread_cancel()によってスレッド実行をキャンセルする
  • 任意のスレッドがexit()を実行するか、メインスレッドがmain()からリターンする

void pthread_exit(void *retval);
retval: 自スレッドの戻り値

pthread_exitはスレッド関数からのreturnと等価だが、スレッド内の任意の関数から実行できる。

スレッドID

自スレッドのIDを取得する関数
pthread_t pthread_self(void);

2つのスレッドIDが等しいかを検査する
int pthread_equal(pthread_t t1, pthread_t t2);

スレッドの終了を待つ

プロセスのwait的な関数
int pthread_join(pthread_t thread, void **retval);

retval: スレッドの戻り値を代入する変数のポインタ。スレッドのreturn/pthread_exit()で返される値

デタッチしていないスレッドはpthread_join()によりjoinする必要がある
joinしないとゾンビスレッドとなり、システムリソースを無駄にしたり新規スレッドを立ち上げられなくなったりする

pthread_join()とwaitpidの違い

  • スレッド間に親子関係はない
    • スレッドAがスレッドB, スレッドBがスレッドCを作成したとき、スレッドBがAをjoinしたりできる
  • 任意のスレッドをjoinする方法は存在しない
    • wait()とかwaitpid(-1, &status, options)で任意の子プロセスをwaitできたが、スレッドの場合は常に対象を指定する。
    • プログラムは自身が把握しているスレッドのみをjoinすべきである。任意のスレッドをjoin可能にすると、ライブラリ内に隠蔽されているスレッド含めすべてのスレッドがjoin可能になってしまう。

スレッドのデタッチ

デタッチしていないスレッドはpthread_join()によりjoinする必要がある
joinしないとゾンビスレッドとなり、システムリソースを無駄にしたり新規スレッドを立ち上げられなくなったりする

スレッドの終了状態を知る必要がないときに、終了したスレッドが自動的に破棄されるようにする.

int pthread_detach(pthread_t thread);

スレッドの終了方法はデタッチされていないスレッドと何も変わらない。プロセスから独立するわけでもない。ただスレッドの終了状態を得られなくなる。

スレッド属性

(他にもあるが)以下のようなものがある

  • スタックのアドレスおよびサイズ
  • スレッドスケジューリングポリシーおよび優先度
  • join可能/デタッチ済み状態

マルチプロセスとマルチスレッド

マルチスレッド

  • 長所
    • スレッド間のデータ共有が容易
    • スレッドはプロセスよりも高速に作成できる(10倍の速度)
  • 短所
    • 使用する関数がスレッドセーフでないといけない。
    • アドレス空間やその他属性を全スレッドで共有する。1スレッドのバグが全スレッドに影響を及ぼす
    • 全スレッドがプロセス内の有限な仮想アドレス空間を奪い合う。
      • スレッドスタックやスレッド固有データは仮想アドレス空間を占有し、ほかのスレッドからは使用できない。
      • プロセスでは1プロセスが仮想メモリ全体を使用できる。

ここから、Javaは一番最初にヒープ領域をガッと確保して、その領域からはみ出る場合はGC→OOMというような感じでプロセスが死ぬ。めっちゃスレッドを作ってヒープを奪い合う。Javaは勝手に子プロセスを生成して高速化するみたいなことは多分してない。

その他

  • マルチスレッドでのシグナル処理は慎重を要する。一般論として、マルチスレッドアプリケーションではシグナルは使わないほうがいい。
  • マルチスレッドでは、全スレッドが同じプログラムを実行数する。
    • マルチプロセスでは、プロセスが異なれば異なるプログラムを実行可能。
  • スレッドは、データ以外にも共有する情報がある。
    • fd, シグナル動作、cwd, uid, gidなど

mutexのダイナミックな初期化

スタティックな初期値PTHREAD_MUTEX_INITIALIZERを使用できるのは、スタティックに割り当て、デフォルトの属性を持つmutexに対してのみ。

static pthread_mutex_t mtx = PTHREAD_MUTEX_INITIALIZER;

ダイナミックな初期化はpthread_mutex_init()を用いる。

  • mutexオブジェクトをヒープ上に動的に割り当てた場合
    • 動的に割り当てた構造体リストがあり、構造体が自信を保護するためのpthread_mutex_tフィールドを持つなど
  • mutexオブジェクトがスタック上に割り当てられているオート変数
  • mutexオブジェクトはスタティックに割り当てたが、デフォルト以外の属性で初期化する

mutexをもう使用しない場合は破棄する必要がある。(PTHREAD_MUTEX_INITIALIZERで初期化したものは破棄不要)。
アンロック状態 かつ どのスレッドもロックしようとしていないものに限り安全に破棄できる。
ダイナミックに割り当てたメモリ内にmutexが存在する場合は、そのメモリを解放する前に破棄する必要がある。
int pthread_mutex_destroy(pthread_mutex_t *mutex);

条件変数

  • mutexは複数のスレッドが同時に共有リソースへアクセスするのを制御する。
  • 条件変数は共有リソースの条件変化を1スレッドから他スレッドへ通知する。

Discussion