【问题标题】:signal handler in child thread子线程中的信号处理程序
【发布时间】:2016-08-09 16:18:25
【问题描述】:

我尝试在下面的代码中为子线程安装 SIGINT 处理程序。我希望子线程在从父进程接收到 SIGINT 时打印 hello 。但是,什么都没有出来,程序立即退出。

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

typedef struct proxy_node_t{
    pthread_t sub_thread;
    pthread_t p_self;
}proxy_node;

proxy_node* proxy;

static void proxy_singnal_handler(){
    printf("Hello\n");
    return;
}

static void* t_consensus(void *arg){
    signal(SIGINT,proxy_singnal_handler);
    sleep(1);
    return NULL;
}

int main(int argc, char **argv)
{
    proxy = (proxy_node*)malloc(sizeof(proxy_node));
    proxy->p_self = pthread_self();
    pthread_create(&proxy->sub_thread,NULL,t_consensus,NULL);
    pthread_kill(proxy->sub_thread,SIGINT);
    sleep(1);
    return 0;
}

【问题讨论】:

  • printf() 不是异步安全的。您不能从信号处理程序中调用它。此外,在创建的线程调用signal() 和主线程调用pthread_kill() 之间存在竞争。

标签: c multithreading pthreads signals


【解决方案1】:

有几个问题。

1) 信号处理程序签名不正确。它应该采用int,而您在没有参数的情况下定义它。 即

static void proxy_singnal_handler(){

应该是

static void proxy_singnal_handler(int sig){

2) 您不能从信号处理程序(在您的情况下为 printf())调用不是 async-signal-safe 的函数。有关详细信息,请参阅signal(7)。您可以改为使用write(2) 打印该消息:

printf("Hello\n");

可以是:

write(1, "Hello\n", 6);

3) 当main 线程发送SIGINT 时,t_consensus 线程可能还没有启动。所以,signal() 可能还没有安装。因此,您需要确保signal() 已安装之前 pthread_kill() 可以发送SIGINT


为了演示,我添加了一些睡眠调用(参见代码中的 cmets)。但请注意, sleep() 不是同步的好方法,如果你打算修改这个例子,那么你应该使用条件变量。

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

typedef struct proxy_node_t{
    pthread_t sub_thread;
    pthread_t p_self;
}proxy_node;

proxy_node* proxy;

static void proxy_singnal_handler(int sig){
    write(1, "Hello\n", 6);
    return;
}

static void* t_consensus(void *arg){
    signal(SIGINT,proxy_singnal_handler);
    while(1); /* infinite loop */
    return NULL;
}

int main(int argc, char **argv)
{
    proxy = (proxy_node*)malloc(sizeof(proxy_node));
    proxy->p_self = pthread_self();
    pthread_create(&proxy->sub_thread,NULL,t_consensus,NULL);
    sleep(2); /* delay to ensure signal handler is installed */
    pthread_kill(proxy->sub_thread,SIGINT);
    sleep(2); /* delay to ensure signal gets executed before the process exits */
    return 0;
}

【讨论】:

  • 您提议的代码表现出未定义的行为:exit()(或等效地,从main() 返回)不是线程安全的。我推荐几个信号量。
  • @EOF exit() 终止任何进程,无论线程数如何。
  • @MaximEgorushkin:C 标准实际上并没有说明在调用 exit() 时运行线程会发生什么。即使我们假设 exit() 在其他线程中导致与 pthread_cancel() 等效且具有异步可取消性,但无论如何想像都不安全。
  • @EOF C 标准未指定它。 POSIX 非常清楚:pubs.opengroup.org/onlinepubs/9699919799/functions/…
  • @EOF OP 可以让线程优雅退出,而不是使用无限循环或使用 _Exit/_exit。
【解决方案2】:

因为@Maxim Egorushkin 希望看到一个优雅退出并使用信号量的解决方案:

#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <unistd.h>
#include <signal.h>
#include <semaphore.h>

typedef struct proxy_node_t{
    pthread_t sub_thread;
    pthread_t p_self;
}proxy_node;

proxy_node* proxy;

static void proxy_singnal_handler(int sig)
{
    write(1, "Hello\n", 6);
    return;
}

sem_t sema1;
sem_t sema2;

static void* t_consensus(void *arg)
{
  signal(SIGINT,proxy_singnal_handler);
  sem_post(&sema1);   /*notify main thread that signal-handler is installed*/
  sem_wait(&sema2);   /*ensure thread exists to be pthread_kill'ed, could use sigsuspend instead*/
  return NULL;
}
 int main(int argc, char **argv)
{
  sem_init(&sema1, 0, 0);
  sem_init(&sema2, 0, 0);
  proxy = (proxy_node*)malloc(sizeof(proxy_node));
  proxy->p_self = pthread_self();
  pthread_create(&proxy->sub_thread,NULL,t_consensus,NULL);
  sem_wait(&sema1);    /*wait until the thread has installed the signal handler*/
  pthread_kill(proxy->sub_thread,SIGINT);
  sem_post(&sema2);    /*not strictly necessary if the thread uses sigsuspend*/
  pthread_join(proxy->sub_thread, NULL);
  free(proxy);         /*not strictly necessary before exiting*/
  sem_destroy(&sema1);
  sem_destroy(&sema2);
  return 0;
}

【讨论】:

  • 第一个信号量用于等待子线程启动。第二个用于使该子线程进入睡眠状态,然后在从sem_wait(&amp;sema2) 返回时取消该线程。目前尚不清楚这些信号量与exit() 有什么关系。而您担心exit() 会引入数据竞赛。
  • @MaximEgorushkin:信号量确保发生的情况有一个顺序:1. main 创建线程 2. 线程安装信号处理程序 3. main 向线程发送信号 4. 线程退出 5 . 主要出口。这些事件不能再以任何其他顺序发生,所以 1. 信号不会意外终止进程,因为信号处置仍然是默认值 2. 在发送信号之前线程不能终止,最后 3. 没有并发线程在运行时主调用退出。
猜你喜欢
  • 1970-01-01
  • 2021-12-21
  • 1970-01-01
  • 1970-01-01
  • 2014-12-09
  • 2015-09-16
  • 2012-01-24
  • 2011-11-18
  • 2014-08-29
相关资源
最近更新 更多