スレッド間通信とスレッド単位タイマーを両立する

これってどうやって実装するんだろう？と迷ったのでメモ

# やりたいこと 
「親スレッドが子スレッドを呼び出したとき、または子スレッドが周期的に処理を実行する」ようなマルチスレッドプログラムを実装したい。（C言語）


# 結論
Linux環境下に限定するならば、 

- 親スレッド→子スレッドへの呼び出し：pthread_killでシグナル送出
- 子スレッド内でのタイマー：timer_createでsigev_notifyにSIGEV_THREAD_IDを指定して、自スレッドにだけシグナル送出
- 子スレッドはsignal_waitで、親スレッドからの呼び出しシグナルと、タイマー満了シグナルを待機


# 環境
CentOS Linux release 7.9.2009 (Core)
gcc 4.8.5 20150623 (Red Hat 4.8.5-44)
glibc.x86_64 2.17-317.el7


# 実装
```C:sigwait_test.c
#include <pthread.h>
#include <stdio.h>
#include <signal.h>
#include <sys/time.h>
#include <sys/syscall.h>

// 終了フラグ（1=子スレッド終了）
// プロセス終了時、子スレッドを先に停止するために使用する
int endProcFlag = 0;

// 子スレッド間共通なので、mainで初期化
sigset_t set;                           // ブロッキング対象のシグナルを示すシグナル集合体
struct itimerspec itval;                // タイマーのインターバル


/* マイクロ秒まで標準出力に表示 */
static void printDateTime()
{
    struct timeval myTime;
    struct tm *time_st;

    gettimeofday(&myTime, NULL);
    time_st = localtime(&myTime.tv_sec);

    printf("[%02d:%02d:%02d.%06d] ", time_st->tm_hour, time_st->tm_min, time_st->tm_sec, myTime.tv_usec);
}

/* 子スレッド：5秒周期でアラーム、または親スレッドからの割り込みで処理実行 */
static void * thread_func()
{
    int sig;
    timer_t tid;
    struct sigevent se;

    // タイマー満了時の動作設定
    se.sigev_notify = SIGEV_THREAD_ID;
    se.sigev_signo = SIGALRM;
    // se.sigev_notify_thread_id = gettid();     ★ この記述だとコンパイルエラー（後述）
    // se._sigev_un._tid = gettid();             ★ 環境によっては、この記述だとコンパイルエラー（後述）
    se._sigev_un._tid = syscall(SYS_gettid);

    timer_create(CLOCK_REALTIME, &se, &tid);

    timer_settime(tid, 0, &itval, NULL);

    for (;;)
    {
        sigwait(&set, &sig);

        // 終了フラグが立っていたら、スレッドを終了する
        if (endProcFlag == 1)
        {
            printDateTime();
            printf("Detect end flag. (thread id = 0x%lx)\n", pthread_self());
            pthread_exit(NULL);
        }

        printDateTime();
        printf("Signal %d received. (thread id = 0x%lx)\n", sig, pthread_self());

        // ここで、SIGUSR1またはタイマー経過時に動かしたい処理

        // 最後の処理起動からタイマー時間後（この例ならば5秒後）に立ち上げる
        timer_settime(tid, 0, &itval, NULL);
    }
}

/* 親スレッド：任意のタイミングで子スレッドにSIGUSR1を配送する */
int main()
{
    pthread_t thread1, thread2;
    time_t timeval;
    int sig;

    // タイマーのインターバル設定（初回：5秒後にアラーム、2回目以降：5秒経過後にアラーム）
    itval.it_value.tv_sec = 5;
    itval.it_value.tv_nsec = 0;
    itval.it_interval.tv_sec = 5;
    itval.it_interval.tv_nsec = 0;

    // SIGUSR1、SIGALRMをブロッキング
    sigemptyset(&set);
    sigaddset(&set, SIGALRM);
    sigaddset(&set, SIGUSR1);
    pthread_sigmask(SIG_BLOCK, &set, NULL);

    pthread_create(&thread1, NULL, &thread_func, NULL);

    printDateTime();
    printf("Thread 1 (0x%lx) start.\n", thread1);

    sleep(7);

    pthread_kill(thread1, SIGUSR1);

    pthread_create(&thread2, NULL, &thread_func, NULL);

    printDateTime();
    printf("Thread 2 (0x%lx) start.\n", thread2);

    sleep(8);

    pthread_kill(thread2, SIGUSR1);

    sleep(8);

    // 終了処理：フラグを立てて即時シグナル送信
    endProcFlag = 1;
    pthread_kill(thread1, SIGUSR1);
    pthread_kill(thread2, SIGUSR1);

    sleep(1);

    return 0;
}
```

# 実行結果
※シグナル番号10=SIGUSR1、14=SIGALRM

```
[root@localhost ~]# ./a.out
[12:21:01.322821] Thread 1 (0x7fcc061c4700) start.
[12:21:06.323435] Signal 14 received. (thread id = 0x7fcc061c4700)
[12:21:08.323347] Thread 2 (0x7fcc059c3700) start.
[12:21:08.323436] Signal 10 received. (thread id = 0x7fcc061c4700)
[12:21:13.323635] Signal 14 received. (thread id = 0x7fcc061c4700)
[12:21:13.323815] Signal 14 received. (thread id = 0x7fcc059c3700)
[12:21:16.323664] Signal 10 received. (thread id = 0x7fcc059c3700)
[12:21:18.323921] Signal 14 received. (thread id = 0x7fcc061c4700)
[12:21:21.323857] Signal 14 received. (thread id = 0x7fcc059c3700)
[12:21:23.324125] Signal 14 received. (thread id = 0x7fcc061c4700)
[12:21:24.323844] Detect end flag. (thread id = 0x7fcc061c4700)
[12:21:24.324352] Detect end flag. (thread id = 0x7fcc059c3700)
[root@localhost ~]#
```
図示するとこんな感じ：
![sigwait_test.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2522478/535e2fd2-248d-1611-a0c3-90602e6dd080.jpeg)


# ポイント
## SIGEV_THREAD_ID
timer_createの第二引数に渡すsigevent構造体には、イベント通知方法（この場合、タイマー時間が経過したことの通知方法）として以下のものが選べます。
（参照：[man page of sigevent](https://linuxjm.osdn.jp/html/LDP_man-pages/man7/sigevent.7.html)）

- SIGEV_NONE  
何もしない
- SIGEV_SIGNAL  
プロセスにシグナルを送信する
（timer_createで第二引数にNULLを渡したときの初期値）
- SIGEV_THREAD  
新しいスレッドを立ち上げて関数を開始する
- SIGEV_THREAD_ID（Linux固有）  
指定されたスレッドにシグナルを送信する


移植性を無視できるならば、SIGEV_THREAD_IDを使うことで、timer_settimeのアラームを自分自身のスレッドにのみ飛ばすことができそうです。


## スレッドIDの指定
シグナルを送りたい先のスレッドIDは、sigevent構造体の「sigev_notify_thread_id」に設定せよと書かれています。
そこで、
`se.sigev_notify_thread_id = gettid();`
と記述すると、「sigevent構造体にsigev_notify_thread_idというメンバーはない」と怒られます。

[同じような悩みを持つ人がいました。](https://stackoverflow.com/questions/16826898/error-struct-sigevent-has-no-member-named-sigev-notify-thread-id) 
ここからリンクされているsiginfo.hはリンク切れになっていますが、[github上のsiginfo.h](https://github.com/torvalds/linux/blob/master/include/uapi/asm-generic/siginfo.h#L333)を見ると、
確かにman page of sigeventに書かれたsigevent構造体とは違うのがわかります。

ということで、
`se._sigev_un._tid = gettid();`
に直します。


## 自スレッドのスレッドIDを取得
[man page of timer_create](https://man7.org/linux/man-pages/man2/timer_create.2.html)には、「sigev_notify_thread_idには、カーネルのスレッドID（すなわち、clone()やgettid()の戻り値）を指定する」と書いています。
が、
`se._sigev_un._tid = gettid();`
と書いてもコンパイルエラーになります。

[man page of gettid](http://linuxjm.osdn.jp/html/LDP_man-pages/man2/gettid.2.html)を読むと、
> The gettid() system call first appeared on Linux in kernel 2.4.11. 
> Library support was added in glibc 2.30. (Earlier glibc versions did not provide a wrapper for this system call, necessitating the use of syscall(2).)  
  
ということで、glibc 2.30未満の場合は、syscall経由で呼び出す必要があるとのことです。
そのため、
`se._sigev_un._tid = syscall(SYS_gettid);`
という記述になっています。

※glibc 2.30のリリースが2019/8/1みたいなので、少しでも古いOSという自覚があれば要注意