【问题标题】:pthread_sigmask() not work in multithreaded programpthread_sigmask() 在多线程程序中不起作用
【发布时间】:2020-11-01 12:48:39
【问题描述】:

我是 c 开发的新手。最近在学习多线程开发的时候注意到一个问题,在Action的主线程中设置信号,在子线程中尝试阻塞主线程设置的信号动作时,发现确实如此不工作。

这里是代码的简要说明

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

void *thread_start(void *_arg) {
  sleep(2);

  sigset_t mask;
  sigemptyset(&mask);
  sigaddset(&mask, SIGUSR2);
  pthread_sigmask(SIG_BLOCK, &mask, NULL);

  printf("child-thread executed\n");

  while (true) {
    sleep(1);
  }

  return NULL;
}

void sig_handler(int _sig) {
  printf("executed\n");
}

int main(int argc, char *argv[]) {
  pthread_t t_id;
  int s = pthread_create(&t_id, NULL, thread_start, NULL);
  if (s != 0) {
    char *msg = strerror(s);
    printf("%s\n", msg);
  }

  printf("main-thread executed, create [%lu]\n", t_id);

  signal(SIGUSR2, sig_handler);

  while (true) {
    sleep(1);
  }

  return EXIT_SUCCESS;
}

【问题讨论】:

  • 当您向该进程发送SIGUSR2 信号时,它将使用主线程传递(因为它在子线程中被阻塞)。此外,您应该使用sigaction(),而不是signal(),正如man 2 signal 手册页所建议的那样。
  • 您的问题是什么?你能告诉我们“它不起作用”是什么意思吗?你没有向我们展示你是如何产生信号的。你做什么,确切地说,你看到了什么,确切地说,你期望有什么不同? (另外,一个 pthread_t 不能保证是数字——在 printf() 中没有意义。)

标签: c linux multithreading pthreads signals


【解决方案1】:

正如我在评论中所写,发送到进程的任何 USR1 信号都将使用主线程传递。它的输出不会告诉你到底发生了什么,所以它不是测试线程和信号掩码的好方法。此外,它在信号处理程序中使用printf(),这可能会或可能不会起作用:printf() 不是an async-signal safe 函数,因此不得在信号处理程序中使用它。

这是一个更好的例子:

#define _POSIX_C_SOURCE 200809L
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
#include <pthread.h>
#include <limits.h>
#include <string.h>
#include <errno.h>
#include <stdio.h>

/* This function writes a message directly to standard error,
   without using the stderr stream.  This is async-signal safe.
   Returns 0 if success, errno error code if an error occurs.
   errno is kept unchanged. */
static int write_stderr(const char *msg)
{
    const char *end = msg;
    const int   saved_errno = errno;
    int         retval = 0;
    ssize_t     n;

    /* If msg is non-NULL, find the string-terminating '\0'. */
    if (msg)
        while (*end)
            end++;

    /* Write the message to standard error. */
    while (msg < end) {
        n = write(STDERR_FILENO, msg, (size_t)(end - msg));
        if (n > 0) {
            msg += n;
        } else
        if (n != 0) {
            /* Bug, should not occur */
            retval = EIO;
            break;
        } else
        if (errno != EINTR) {
            retval = errno;
            break;
        }
    }

    /* Paranoid check that exactly the message was written */
    if (!retval)
        if (msg != end)
            retval = EIO;

    errno = saved_errno;
    return retval;
}

static volatile sig_atomic_t  done = 0;
pthread_t                     main_thread;
pthread_t                     other_thread;

static void signal_handler(int signum)
{
    const pthread_t  id = pthread_self();
    const char *thread = (id == main_thread) ? "Main thread" :
                         (id == other_thread) ? "Other thread" : "Unknown thread";
    const char *event = (signum == SIGHUP) ? "HUP" :
                        (signum == SIGUSR1) ? "USR1" :
                        (signum == SIGINT) ? "INT" :
                        (signum == SIGTERM) ? "TERM" : "Unknown signal";

    if (signum == SIGTERM || signum == SIGINT)
        done = 1;

    write_stderr(thread);
    write_stderr(": ");
    write_stderr(event);
    write_stderr(".\n");
}

static int install_handler(int signum)
{
    struct sigaction  act;
    memset(&act, 0, sizeof act);
    sigemptyset(&act.sa_mask);
    act.sa_handler = signal_handler;
    act.sa_flags = 0;
    if (sigaction(signum, &act, NULL) == -1)
        return -1;
    return 0;
}

void *other(void *unused __attribute__((unused)))
{
    sigset_t  mask;

    sigemptyset(&mask);
    sigaddset(&mask, SIGTERM);
    sigaddset(&mask, SIGHUP);
    pthread_sigmask(SIG_BLOCK, &mask, NULL);

    while (!done)
        sleep(1);

    return NULL;
}

int main(void)
{
    pthread_attr_t  attrs;
    sigset_t        mask;
    int             result;

    main_thread = pthread_self();
    other_thread = pthread_self(); /* Just to initialize it to a sane value */

    /* Install HUP, USR1, INT, and TERM signal handlers. */
    if (install_handler(SIGHUP) ||
        install_handler(SIGUSR1) ||
        install_handler(SIGINT) ||
        install_handler(SIGTERM)) {
        fprintf(stderr, "Cannot install signal handlers: %s.\n", strerror(errno));
        return EXIT_FAILURE;
    }

    /* Create the other thread. */
    pthread_attr_init(&attrs);
    pthread_attr_setstacksize(&attrs, 2*PTHREAD_STACK_MIN);
    result = pthread_create(&other_thread, &attrs, other, NULL);
    pthread_attr_destroy(&attrs);
    if (result) {
        fprintf(stderr, "Cannot create a thread: %s.\n", strerror(result));
        return EXIT_FAILURE;
    }

    /* This thread blocks SIGUSR1. */
    sigemptyset(&mask);
    sigaddset(&mask, SIGUSR1);
    pthread_sigmask(SIG_BLOCK, &mask, NULL);

    /* Ready to handle signals. */
    printf("Send a HUP, USR1, or TERM signal to process %d.\n", (int)getpid());
    fflush(stdout);

    while (!done)
        sleep(1);

    pthread_join(other_thread, NULL);

    return EXIT_SUCCESS;
}

另存为,例如example.c,编译运行使用

gcc -Wall -O2 example.c -pthread -o exprog
./exprog

它将阻塞主线程中的 USR1 信号,以及另一个线程中的 HUP 和 TERM 信号。它还将捕获 INT 信号 (Ctrl+C),该信号在任何一个线程中都不会被阻塞。当您向其发送 INT 或 TERM 信号时,程序将退出。

如果您向程序发送 USR1 信号,您会看到它始终会使用其他线程传递。

如果你向程序发送一个 HUP 信号,你会看到它总是使用主线程传递。

如果你向程序发送一个 TERM 信号,它也将使用主线程传递,但它也会导致程序退出(很好)。

如果您向程序发送一个 INT 信号,它将使用其中一个线程传递。这取决于几个因素,您是否总是会看到它使用同一个线程交付,但至少在理论上,它可以使用任一线程交付。这个信号也会导致程序退出(很好)。

【讨论】:

  • 对于大多数 GCC 的实现,使用 -pthread 优于“仅”使用 -lpthread
  • 感谢您如此耐心的回复!请查看已完成的回复here
  • @NGPONG:当您向进程发送信号(使用kill()kill shell 命令),并且该进程已为其安装了信号处理程序时,内核将使用进程中没有阻塞信号的任何线程。要将信号定向到特定线程,您需要在同一进程中使用pthread_kill()pthread_sigqueue()。 (Linux 确实支持tkill(),但要使用它,您需要找到目标线程的 tid。)
【解决方案2】:

信号掩码是一个每线程属性,线程将继承父线程在创建线程时拥有的任何东西,但之后,它控制它的拥有副本。

换句话说,阻塞线程中的信号会影响该线程的信号传递,不会影响任何其他线程。

在任何情况下,即使它共享(不是),你也会有一个潜在的竞争条件,因为你在主线程中设置信号之前启动了子线程。因此,不确定顺序是“父设置信号,然后是子块”,反之亦然。但是,如上所述,由于信号掩码的线程特定性质,这无关紧要。

如果你想让一个线程控制另一个线程的信号掩码,你需要使用某种形式的线程间通信让另一个线程自己做。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2020-05-24
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-08-07
    • 1970-01-01
    相关资源
    最近更新 更多