2014-07-08 225 views
11

我無法獲得線程來捕獲正確的信號。Unix線程和信號:每個線程的信號處理程序

例如,

我首先啓動一個主線程(tid 1)。

然後,它使用signal(2)將函數SIGUSR1的信號處理函數設置爲function1()。

主線程創建新線程,與TID 2.

在螺紋2,I寄存器使用signal(2)SIGUSR1function2()用於信號處理程序。

線程1然後創建一個線程3(tid 3)。

從線程3,我用pthread_kill(1, SIGUSR1)發送信號到線程1

然而,function2()被調用,而不是function1()

這是行爲的目的,還是有什麼我需要改變,讓這些信號處理程序的工作?

編輯:我做了一些調試,結果發現信號被髮送到線程1,但是function2()由於某種原因從線程1被調用。有沒有解決方法?

+3

信號是一個進程調用,而不是每個線程一個,如果你調用它,它會設置進程中所有線程的處理程序。信號和線程是一個複雜的話題。你最好問自己該做什麼。 –

+3

在多線程程序中使用'signal'基本上是未定義的行爲。它在手冊中是這樣說的。 –

+0

源代碼請。 –

回答

7

除了alk's answer

您可以使用每線程功能以某個線程的方式選擇在某個信號傳遞時執行哪個函數的指針。

注意:信號傳送到任何線程,不會明確阻止其傳送。這不會改變這一點。您仍然需要使用pthread_kill()或類似的機制將信號引導至特定的線程;發出或發送到進程(而不是特定線程)的信號仍將由隨機線程處理(在不阻止它的那些線程中)。

我想不出任何用例,我個人更喜歡這種方法;到目前爲止,一直有其他方式,其他方式更容易和更好。因此,如果您正在考慮爲實際應用程序實施此類功能,請退後一步並重新考慮您的應用程序邏輯。

但是,因爲該技術是可能,這裏是如何我可能會實現它:

#include <signal.h> 

/* Per-thread signal handler function pointer. 
* Always use set_thread_SIG_handler() to change this. 
*/ 
static __thread void (*thread_SIG_handler)(int, siginfo_t *, void *) = (void *)0; 

/* Process-wide signal handler. 
*/ 
static void process_SIG_handler(int signum, siginfo_t *info, void *context) 
{ 
    void (*func)(int, siginfo_t *, void *); 

#if (__GNUC__ > 4) || (__GNUC__ == 4 && __GNUC_MINOR__ >= 7) 
    func = __atomic_load_n(&thread_SIG_handler, __ATOMIC_SEQ_CST); 
#else 
    func = __sync_fetch_and_add(&thread_SIG_handler, (void *)0); 
#endif 

    if (func) 
     func(signum, info, context); 
} 

/* Helper function to set new per-thread signal handler 
*/ 
static void set_thread_SIG_handler(void (*func)(int, siginfo_t *, void *)) 
{ 
#if (__GNUC__ > 4) || (__GNUC__ == 4 && __GNUC_MINOR__ >= 7) 
    __atomic_store_n(&thread_SIG_handler, func, __ATOMIC_SEQ_CST); 
#else 
    void (*oldfunc)(int, siginfo_t *, void *); 
    do { 
     oldfunc = thread_SIG_handler; 
    } while (!__sync_bool_compare_and_swap(&thread_SIG_handler, oldfunc, func)); 
#endif 
} 

/* Install the process-wide signal handler. 
*/ 
int install_SIG_handlers(const int signum) 
{ 
    struct sigaction act; 
    sigemptyset(&act.sa_mask); 
    act.sa_sigaction = process_SIG_handler; 
    act.sa_flags = SA_SIGACTION; 
    if (sigaction(signum, &act, NULL)) 
     return errno; 
    return 0; 
} 

我喜歡上面的,因爲它不需要並行線程,並且非常穩定和可靠的。除了由於具有預處理器邏輯來選擇使用哪種類型的原子內置插件而造成的視覺混亂之外,如果仔細觀察,它也非常簡單。

GCC 4.7及更高版本提供C++ 11-like __atomic built-ins,較老的GCC版本和其他編譯器(ICC,Pathscale,Portland Group)提供__sync legacy built-ins。用於線程本地存儲的__thread keyword應該類似地在所有當前POSIX-y系統中可用。

如果你有一個陳舊的系統,還是堅持標準的符合性,下面的代碼應該大致相當於行爲:

#include <pthread.h> 
#include <signal.h> 
#include <errno.h> 

static pthread_key_t thread_SIG_handler_key; 

static void process_SIG_handler(int signum, siginfo_t *info, void *context) 
{ 
    void (*func)(int, siginfo_t *, void *); 

    *((void **)&func) = pthread_getspecific(thread_SIG_handler_key); 
    if (func) 
     func(signum, info, context); 
} 

static int set_thread_SIG_handler(void (*func)(int, siginfo_t *, void *)) 
{ 
    sigset_t block, old; 
    int result; 

    sigemptyset(&block); 
    sigaddset(&block, SIG); /* Use signal number instead of SIG! */ 
    result = pthread_sigmask(SIG_BLOCK, &block, &old); 
    if (result) 
     return errno = result; 

    result = pthread_setspecific(thread_SIG_handler_key, (void *)func); 
    if (result) { 
     pthread_sigmask(SIG_SETMASK, &old, NULL); 
     return errno = result; 
    } 

    result = pthread_sigmask(SIG_SETMASK, &old, NULL); 
    if (result) 
     return errno = result; 

    return 0; 
} 

int install_SIG_handlers(const int signum) 
{ 
    struct sigaction act; 
    int result; 

    result = pthread_key_create(&thread_SIG_handler_key, NULL); 
    if (result) 
     return errno = result; 

    sigemptyset(&act.sa_mask); 
    act.sa_sigaction = process_SIG_handler; 
    act.sa_flags = SA_SIGACTION; 
    if (sigaction(signum, &act, NULL)) 
     return errno; 

    return 0; 
} 

我覺得現實生活中的等價最接近像這樣的代碼,我其實已經曾經使用過的一個例子是,我用一個實時信號(SIGRTMIN+0)在一個線程中被阻塞,作爲反射器:它發送另一個實時信號(SIGRTMIN+1)給多個工作線程,以中斷阻塞I/O。 (可以用一個實時信號做到這一點,但雙信號模型實現起來更簡單並且更容易維護。)

這樣的信號反射或扇出有時是有用的,並且它沒有什麼不同這種方法。儘管如此,如果有人對此感興趣,還是有足夠的差別來保證自己的問題。

+0

你對我的設計進行反思是絕對正確的。我選擇使用pthread_cancel,因爲它似乎很適合我的需求。但是,您的列表中可能存在一個小問題。 ''pthread_getspecific()'根據手冊頁不是異步信號安全的,我不認爲'__thread'存儲訪問也是。 – tomKPZ

+0

@ user1887231:實際上,[它們是](http://sourceware.org/ml/libc-alpha/2012-06/msg00372.html)。這些標準還沒有解決異步信號安全線程特定變量的問題。至少在GNU工具中,'pthread_getspecific()'和'__thread'變量在實踐中是異步信號安全的*。 (在這裏,信號處理程序只讀取特定於線程的變量,所以「不在TLS之外」的情況不適用)。如果您擔心這一點,請告訴我,我會想出一個解決方法(我想我知道一種方式)。 –

4

不可能安裝「每線程」信號處理程序。

man 7 signal(由我強調):

的信號配置是每進程屬性:在多線程應用程序,一個特定信號的配置是所有線程的相同。

它然而能夠指導每個信號型到一個不同的線程,通過在「每線程」基地屏蔽掉任何數量的信號類型的接收。

在如何將一組信號類型的定向到一個特定的線程,你可能會喜歡有看看這個答案:https://stackoverflow.com/a/20728819/694576