2015年1月1日 星期四

thread 和 signal

光是 signal 或是只有 thread 就夠複雜了, 把他們混在一起更是剪不斷、理還亂; 若再加上 fork 就更精彩了。

Pthreads programming chapter 5 (中文版) 寫的非常清楚, 讓我們知道當 signal 加入了 thread 時, 程式員會遇到的困難, 若你的程式有奇怪的現象, 很有可能是遇上這些麻煩。當然 thread 麻煩的問題很多, 也可能是其他的問題。

當一個 signal 來的時候, 你知道是哪個 thread 被暫停下來處理這個 signal 嗎?

有時候可能是 thread 1 處理, 有時候可能是 thread 2 處理, 而你確定不管用哪個 thread 處理, 程式都能正確執行嗎? 也許你有自信, 但事實上可能和你想的不一樣。

在我的案例上, 會造成程式無法正常的結束, 程式可以結束, 但不是正常結束, 這帶來了一點小問題。

有好幾種方法可以處理這樣的情形, 可以指定某個 signal 讓某個 thread 處理, 不過要對 signal 和 thread 有進階的理解, 可能會花點腦力, 還要有一本好書。man pthread_sigmask 有一個好例子 (th.c 是我從 man 上抄來可以 compile 的完整程式) 可以說明這件事情。這只是其中一種方法。

這個方法是先把 signal SIGUSR1, SIGTERM 擋下來 (其實就是先 queue 起來), 不讓 signal handle 立刻出動, 讓 sig_thread 這個 thread 呼叫 sigwait 取出被擋下來的 SIGUSR1 或是 SIGTERM, 然後由 sig_thread 這個 thread 處理。

我測試了兩個平台:
  • mips linux 2.6.36
  • Debian x86 3.14.15-2
在 mips linux 2.6.36 平台, 一個 thread 會有不同的 pid。

compile command:
mipsel-linux-gcc th.c -o th -pthread


# pid: 19316
sig thread pid: 19318

ps
19316 root 60 S ./th
19317 root 60 S ./th
19318 root 60 S ./th

x86:

compile command:
gcc th.c -o th -pthread

不同 thread 有相同的 pid, 這是我預期的。
descent@debianlinux:tmp$ pid: 10902
sig thread pid: 10902


th.c
 1 #include <signal.h>
 2 #include <pthread.h>
 3 #include <errno.h>
 4 #include <stdlib.h>
 5 #include <stdio.h>
 6 
 7 /* Simple error handling functions */
 8 
 9 #define handle_error_en(en, msg) \
10                do { errno = en; perror(msg); exit(EXIT_FAILURE); } while (0)
11 
12 static void *
13 sig_thread (void *arg)
14 {
15   sigset_t *set = arg;
16   int s, sig;
17 
18   printf("sig thread pid: %d\n", getpid());
19 
20   for (;;)
21     {
22       s = sigwait (set, &sig);
23       if (s != 0)
24  handle_error_en (s, "sigwait");
25       printf ("Signal handling thread got signal %d\n", sig);
26     }
27 }
28 
29 int
30 main (int argc, char *argv[])
31 {
32   pthread_t thread;
33 
34   sigset_t set;
35   int s;
36 
37   /* Block SIGQUIT and SIGUSR1; other threads created by main()
38      will inherit a copy of the signal mask. */
39 
40   printf("pid: %d\n", getpid());
41 
42   sigemptyset (&set);
43   sigaddset (&set, SIGQUIT);
44   sigaddset (&set, SIGUSR1);
45   s = pthread_sigmask (SIG_BLOCK, &set, NULL);
46   if (s != 0)
47     handle_error_en (s, "pthread_sigmask");
48 
49   s = pthread_create (&thread, NULL, &sig_thread, (void *) &set);
50   if (s != 0)
51     handle_error_en (s, "pthread_create");
52 
53   /* Main thread carries on to create other threads and/or do
54      other work */
55 
56   pause ();   /* Dummy pause so we can test program */
57 }

$ ./a.out &
[1] 5423
$ kill -QUIT %1
Signal handling thread got signal 3
$ kill -USR1 %1
Signal handling thread got signal 10
$ kill -TERM %1
[1]+ Terminated ./a.out

man pthreads 可以得到進一步資訊, linux 上有實作兩種 POSIX threads: NPTL, LinuxThreads。

LinuxThreads:
Threads do not share process IDs.

所以我的疑惑解除了。

還有別的方法, 不過由於這個就搞定我的問題了, 所以只能分享這個方法。最後我得遺憾的告訴你, Pthreads Programming 的中文版絕版了。

2 則留言:

  1. Hi Descent
    按照您的說法
    那段範例的寫法 只能使用在x86平台嗎?
    另外假如是遇到
    Program terminated with signal 4, Illegal instruction.
    您覺得擋得住嗎@@?

    回覆刪除
  2. sigaddset (&set, SIGILL);

    descent@debian-vm:progs$ kill -ILL %2
    descent@debian-vm:progs$ Signal handling thread got signal 4

    我增加了 SIGILL, 看來沒問題。

    在 x86/mips 都可以正常執行, arm 平台我沒測試過。

    回覆刪除

使用 google 的 reCAPTCHA 驗證碼, 總算可以輕鬆留言了。

我實在受不了 spam 了, 又不想讓大家的眼睛花掉, 只好放棄匿名留言。這是沒辦法中的辦法了。留言的朋友需要有 google 帳號。