スレッドの管理

Linuxとpthreadsによるマルチスレッドプログラミング入門を読んでいる。
この記事は第二章のメモ。

スレッドを作る

本に書いてあったことはだいたい知ってたので、man pthread_createしたときのメモを載せてみる。

  • threadは以下のいずれかの終わり方をする
    • スレッド内でpthread_exit()を呼ぶ。このときexit statusを指定できる。
    • pthread_create したときの指定関数の返り値で終わる。これはpthread_exitと同じ。
    • pthread_cancelされる
    • どれからのスレッドでexitが呼ばれる

  • デフォルトのスタックサイズは2MB

  • Linux 2.4からはgetpidするとTGID(thread group identifier)が取得できる。同じプロセス内でスレッドを立ち上げた場合にはTGIDは同じ値なため、同じものが得られる。
    • 実際確かめてみたら同じになった。
スレッドの終了
  • pthreadではスレッド間に優劣や親子関係があるのではなく、プロセス内のすべてのスレッドが原則として対等である
スレッドIDとスレッド終了待ち

man pthread_joinを読む。

  • int pthread_join(pthread_t thread, void **retval);はthreadで指定されたスレッドの終了を待つ。もし既に終わっていたら、すぐpthread_join()は帰ってくる。
  • retvalがNULLでない場合、pthread_join()はスレッドのexit statusをコピーして、*retval で指定される場所に格納する。
  • 複数スレッドが同時にjoinしようとしたときの挙動は不定
  • 一回joinしたあともう一度joinするときの挙動も不定
    • なのでjoinしたらthreadにNULLを入れておき、joinの事前にチェックしておくなど対策要。
  • joinableなスレッドをjoin失敗した時はゾンビスレッドが生まれてしまう。
スレッドの同定
  • pthreadではpthread_self()を使ってスレッドIDを取得できる。
    • LinuxではスレッドIDは単純な数値になっている。しかし、OSやCPUの都合によりpthread_t型は単純な数値型にならないことがありえる。pthread_t型の比較にはpthread_equalを使うことを推奨。

      POSIX.1 allows an implementation wide freedom in choosing the type used to represent a thread ID; for example, representation using either an arithmetic type or a structure is permitted. Therefore, variables of type pthread_t can't portably be compared using the C equality operator (==); use pthread_equal(3) instead.

    • スレッドIDはプロセス内でユニークである。でもスレッド終了後には再利用される可能性がある。
スレッドの属性
  • スレッドの属性については、OSやライブラリのバージョンの違いによって正しく機能しないことがある。デフォルトの属性(NULL)推奨。
他のスレッドの強制停止
  • pthread_cancelをすると他のスレッドを停止できる
    • でもリソースの解放はされない。メモリリークの温床になる。できるだけ使わない。

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

Linuxとpthreadsによるマルチスレッドプログラミング入門を読んでいる。
この記事は第一章のメモ。

pthreadに関する基本的な知識はあるので序盤はあんまりメモしなくて良いかなという感じで読み始めた。

マルチプロセスとマルチスレッドの違い
  • コンテキストスイッチの重さ
    • マルチプロセスの場合、コンテキストスイッチする前にその実行環境(メモリ内容などのリソース状態)を保全して、後で戻ってきた時のメモリ内容などを戻さないといけないため、比較的重たい。
    • マルチスレッドの場合、コンテキストスイッチする際に保全しないといけない情報はプログラムカウンタやスタックポインタなどの極小数の情報だけで済む。高速。
マルチスレッドプログラミングの基本要素
  • pthreadで重要なのは高々15種類
    • その他はOSやライブラリのバージョンによって挙動が違ったり、利用に特別な注意が必要。

  • pthreadには動作を一時停止する関数はない。スレッドでの動作を一定時間停止するには標準ライブラリ内のsleep関数やnanosleep関数を用いる。
  • 他のマルチスレッドシステムでは、他のスレッドの処理の割り込みを許す、という動作を指示する関数が用意されている(yield)。しかしpthreadは原則として「常に他のスレッドの割り込みは許されている」というポリシーで作られているため、yieldに相当する関数は存在しない。

Linuxでスレッドの動作コアを確認する

マルチスレッドプログラミングではテスト時にスレッドの動作コアを確認したい。 その方法をメモ。

システムモニタを使う

sudo apt install indicator-multiload //インストール
indicator-multiload //実行

タスクバーに出てくるシステムモニタをクリックすると、 コアごとの使用率がリアルタイムに見られる。 f:id:yusuke_ujitoko:20190110091259p:plain

https://www.eidos.ic.i.u-tokyo.ac.jp/~tau/lecture/operating_systems/gen/ex/threads_processes/

コマンドライン

psを使う
ps -aF //実行

とすると、

UID        PID  PPID  C    SZ   RSS PSR STIME TTY          TIME CMD
ujitoko   7751  7740  0  5462  2988   6 20:58 pts/2    00:00:00 tmux
ujitoko  17448  9599  5 415600 116896 2 21:30 pts/20   00:00:08 /usr/bin/mono /usr/lib/pinta/Pinta.ex
ujitoko  18018  7754  0  9594  3276   2 21:33 pts/19   00:00:00 ps -aF

プロセスごとの動作しているコアがPSR列に表示される。

htopを使う
sudo apt install htop //インストール
htop //実行

htopを使うとグラフィカルにコア動作状況がわかる。

f:id:yusuke_ujitoko:20190110213215j:plain

シグナルハンドラを書く最小サンプル

#include <signal.h>
#include <stdio.h>
#include <unistd.h>

void sigcatch(int);

int main() {
    pid_t pid = getpid();
    printf("PID:%d\n", pid);

    if (SIG_ERR == signal(SIGHUP, sigcatch)) {
        printf("failed to set signal handler.n");
        exit(1);
    }

    if (SIG_ERR == signal(SIGINT, sigcatch)) {
        printf("failed to set signal handler.n");
        exit(1);
    }

    while (1) {
        sleep(1);
    }

    return 0;
}

void sigcatch(int sig) {
    printf("catch signal %d\n", sig);
    exit(1);
}

実行時に表示されるプロセスIDに対して,kill -INT XXXXXkill -HUP XXXXXをするとシグナルハンドラが実行される.
プロセスIDを調べるときは上記サンプルのように,プロセスIDを標準出力してもよいし, ps u | grep a.outという感じでコマンドを打って確認してもよい.

pthread_detachについて

POSIXスレッドライブラリのdetachの説明が入門用資料を見ていてもなかなか見つからず, ようやく見つかったのでメモ.

スレッドが終了したら,別のスレッドでその終了したスレッドのリソースを開放しなければならない. だが,プログラムによっては,このリソース開放処理を記述する適切な箇所が見当たらなかったり,プログラム構造に制約を課してしまう場合がある. このような場合には,該当スレッドにてpthread_createする際の属性にてデタッチ属性を指定するか(※), include <pthread.h> int pthread_detach(pthread_t th); を呼び出す. 返り値は0なら成功,0以外ならエラーコードが設定されている. 引数にはやはり,デタッチ対象のスレッドの識別子を指定する. スレッドをデタッチすることによって,対象のスレッドは終了時にリソースが開放されることを保証される. ただし,合流可能状態でなくなり,pthread_joinできなくなる. デタッチされたスレッドに対しpthread_joinを実行すると,エラーが返される. また,pthread_joinにて合流待ちされているスレッドに対してpthread_detachを呼び出すと,何も処理されず0が返される.

通常はthreadの処理が終わるとリソースが保持されておりpthread_join時にリソース取得ができるが,
pthread_joinを呼ばない場合にはpthread_detachを呼んでおくとスレッド終了時にリソースが解放される.
もしくはpthread_createするときにdetach属性を付与しておく必要がある.
ということ.