2011-05-25 64 views
3

有一個定時器每隔1秒發出一次信號SIGALARM。睡覺 秒的信號處理程序已註冊。怎麼了?具體來說,我有以下代碼,其中進程運行多個線程。有趣的是,使用這個長信號處理程序,看起來其他線程被阻止執行......任何人都可以解釋爲什麼這是這種情況?帶有長信號處理器(SIGALARM)的定時器問題

#include <unistd.h> 
#include <stdio.h> 
#include <stdlib.h> //rand 
#include <sys/wait.h> 
#include <time.h> 
#include <sys/time.h> 
#include <pthread.h> 
#define NUM_THREADS 2 

int init_timer(int real_time, int msec) { 
    struct itimerval timeslice; 
    timeslice.it_interval.tv_sec = msec/1000; 
    timeslice.it_interval.tv_usec = (msec % 1000) * 1000; 
    timeslice.it_value.tv_sec = msec/1000; 
    timeslice.it_value.tv_usec = (msec % 1000) * 1000; 
    setitimer(real_time ? ITIMER_REAL : ITIMER_VIRTUAL, &timeslice, NULL); 
    return 0; 
} 

void install_handler(int signo, void(*handler)(int)) { 
    sigset_t set; 
    struct sigaction act; 

    /* Setup the handler */ 
    act.sa_handler = handler; 
    act.sa_flags = SA_RESTART; 
    sigaction(signo, &act, 0); 

    /* Unblock the signal */ 
    sigemptyset(&set); 
    sigaddset(&set, signo); 
    sigprocmask(SIG_UNBLOCK, &set, NULL); 
    return; 
} 

void timerTest(int signo) 
{ 
    printf("000\n"); 
    sleep(1); 
    printf("111\n"); 
} 

void * threadTest(void * threadId) 
{ 
    while(true) 
    { 
     printf("222\n"); 
    } 
} 

int main(int argc, char *argv[]) { 
    int real_time = 1; 
    int tick_msec = 10; 
    init_timer(real_time, tick_msec); 
    install_handler(real_time ? SIGALRM : SIGVTALRM, &timerTest); 

    pthread_t threads[NUM_THREADS]; 
    int rc; 
    long t; 
    for (t = 0; t < NUM_THREADS; t++) { 
     rc = pthread_create(&threads[t], NULL, threadTest, (void *) t); 
     if (rc) { 
      exit(-1); 
     } 
    } 

    void * status; 
    for (t = 0; t < NUM_THREADS; t++) { 
     rc = pthread_join(threads[t], &status); 
     if (rc) { 
      exit(-1); 
     } 
    } 
    pthread_exit(NULL); 
} 

打印:

222 
222 
222 
222 
... 
222 
000 
111 
000 
111 
... 

不會有222發生第111後?爲什麼這樣?

+0

在帖子中包含操作系統也可能相關。 – 2011-05-25 19:45:11

回答

4

信號被傳遞到一個特定的線程,所以信號處理程序運行在一個特定的線程(信號傳遞到的線程)。如果信號傳送到寫出222\n的線程,那麼該線程必須停止寫出222\n並運行信號處理程序。您的示例信號處理程序需要一整秒的時間才能運行,因此這是整個秒,在此期間該線程可能不會寫出222\n

此外,由於您正在使用printf來寫出所有這些字節,因此在libc中會執行一些鎖定。由於printf不是「異步信號安全」功能,因此實際上未定義如果您在信號處理程序中使用它,會發生什麼情況。你觀察到的行爲的一個可能的解釋是這個。如果信號在線程持有stdout鎖的同時傳遞給線程,則其他線程將無法寫入stdout,直到處理程序返回並且該鎖可以通過在該線程中運行的「正常」代碼釋放。但是,在這種情況下,信號處理程序仍然可以寫入標準輸出,因爲鎖定是可以重複獲取任何特定線程的一個rlock。不過,這取決於具體的平臺,C庫,線程庫或月相。儘管你的例子很容易轉換爲使用write(2),但它顯示了或多或少相同的問題行爲,或多或少具有相同的修復方法,並且不依賴於未定義的行爲。

如果SIG_BLOCK222\n線程計時器信號,然後將信號處理程序將始終在主線程中運行,你會繼續得到222\n輸出,而信號處理程序休眠。

Seth在使用信號處理程序中的安全函數方面也做了很好的工作。使用任何其他方式意味着您的程序的行爲是不確定的。

+0

stdio鎖定是遞歸的這一事實並不意味着當鎖定已經在調用線程中時,它將從信號處理程序工作。遞歸比可重入要求弱得多,事實上stdio和它使用的鎖定通常都是可重入的。 – 2011-05-25 20:25:27

+0

謝謝,很棒的一點。我已經編輯了答案,明確了printf行爲在這種情況下是未定義的,並指出了一種替代方法,它有點相同,但是避免了未定義的行爲。 – 2011-05-25 20:31:24

+0

觀察到的行爲實際上是由於'printf'的鎖定。將'printf'改爲'write',你會得到一種更爲明智的行爲,即孩子們正在編寫'222',偶爾'000','111',直到所有三個線程都在信號處理器上結束睡眠(並且進一步的信號被阻塞)再寫'222'。 – ninjalj 2011-05-25 20:51:50

2

通常情況下,當您處於信號處理程序中時,信號(或至少該信號)被阻塞。在信號處理器中做非常不好的主意。通常你應該設置一個變量或類似的東西,然後處理信號,一旦你在正常的代碼路徑。

請參閱sigaction的SA_NODEFER標誌,以便在信號處理程序中允許或拒絕接收信號。

還有一些函數可以從信號處理程序中安全地調用。信號(7)手冊頁描述了這一點。 「一個信號處理函數必須非常小心,因爲在其他地方的處理可能會在程序執行的某個任意點被中斷,POSIX的概念是」安全函數「,如果一個信號中斷了一個不安全函數的執行,調用一個不安全的函數,那麼程序的行爲是未定義,其從信號處理程序內部破碎調用一個不安全功能

的程序。在某些機器上,它會 「工作」;在別人身上會發現coredump。它可以做任何事情,包括重新格式化磁盤,讓用戶使用 磨損的Barry Manilow唱片倒退,或者在達拉斯放棄tacnuke 。嘗試調用不安全的函數會將程序置於未定義行爲的黃昏區域。

道歉對鼠標。

+0

如果可以保證它不會中斷不安全的功能,則從信號處理程序調用不安全的函數有時是有效的。例如,如果線程正在執行'for(;;)pause();'那麼你可以在信號處理程序中做任何你想做的事情。 – 2011-05-25 20:27:19

+0

如果只有我說過。等一下!我做到了! – 2011-05-25 20:39:48

+0

我回復了這句話:「一個從信號處理程序中調用不安全函數的程序被破壞了。」這是過分簡單化。大多數天真寫入的程序會這樣做*被破壞*,但這是由於程序員犯錯而不檢查其他函數可能會被中斷,而不是信號處理程序中使用異步信號不安全函數的內在結果。 – 2011-05-25 20:48:07

-1

在信號處理程序中使用printf是不正確的。

關於要處理SIGALARM的線程,您可能會發現有關信號阻塞here的一些有用信息。這篇文章包含啓發Linux內核代碼。本質上,信號由主線程處理,如果它想要接收它的話。如果不是,它將由任何其他需要它的線程處理。您可以使用線程屏蔽特定信號pthread_sigmask(3)

+0

儘管這個鏈接可能回答這個問題,但最好在這裏包含答案的重要部分,並提供供參考的鏈接。如果鏈接頁面更改,則僅鏈接答案可能會失效。 – BenR 2014-01-14 19:52:54

+0

謝謝你的評論,從現在開始我會這樣做 – Konstantin 2014-01-14 20:20:54