为什么 sem_wait 在中断时不会解除阻塞(并返回 -1)?

alp*_*332 9 c semaphore pthreads interrupt interrupt-handling

我有一个使用sem_wait. 该POSIX规范说:

sem_wait()功能可通过信号传送中断。

此外,在关于错误的部分中,它说:

[EINTR] - 一个信号中断了这个功能。

但是,在我的程序中,发送信号不会解除调用的阻塞(并-1按照规范中的指示返回)。

一个最小的例子可以在下面找到。该程序sem_wait在发送信号后挂起并且永远不会解除阻塞。

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

sem_t sem;

void sighandler(int sig) {
  printf("Inside sighandler\n");
}

void *thread_listen(void *arg) {
  signal(SIGUSR1, &sighandler);
  printf("sem_wait = %d\n", sem_wait(&sem));
  return NULL;
}

int main(void) {

  pthread_t thread;

  sem_init(&sem, 0, 0); 

  pthread_create(&thread, NULL, &thread_listen, NULL);

  sleep(1);
  raise(SIGUSR1);

  pthread_join(thread, NULL);

  return 0;
}
Run Code Online (Sandbox Code Playgroud)

程序输出Inside sighandler然后挂起。

还有另外一个问题在这里关于这一点,但它并没有真正提供任何清晰。

我误解了规范所说的内容吗?仅供参考,我的电脑使用 Ubuntu GLIBC 2.31-0ubuntu9。

zwo*_*wol 9

该程序无法按预期运行的原因有三个,其中只有两个是可以修复的。

  1. 正如 David Schwartz 的回答所指出的,在多线程程序中,raise向调用的线程发送一个信号raise.

    为了将信号发送到您想要的线程,在这个测试程序中raise(SIGUSR1)pthread_kill(thread, SIGUSR1). 但是,如果您希望该特定线程在SIGUSR1发送到整个进程时进行处理,您需要做的是使用pthread_sigmask阻塞SIGUSR1所有线程,除了应该处理它的线程。(有关这方面的更多详细信息,请参见下文。)

  2. 在使用 glibc 的系统上,signal安装不会中断阻塞系统调用的信号处理程序。要获得这样做的信号处理程序,您需要使用sigaction并设置sa_flags包含SA_RESTART. 例如,

      struct sigaction sa;
      sigemptyset(&sa.sa_mask);
      sa.sa_handler = sighandler;
      sa.sa_flags = 0;
      sigaction(SIGUSR1, &sa, 0);
    
    Run Code Online (Sandbox Code Playgroud)

    注:memset(&sa, 0, sizeof sa)没有保证具有相同的效果sigemptyset(&sa.sa_mask)

    注意:信号处理程序是进程全局的,因此调用哪个线程并不重要sigaction。在几乎所有情况下,多线程程序都应该在创建任何线程之前完成所有sigaction调用main,以确保信号处理程序在任何信号发生之前处于活动状态。

  3. 信号可以在线程有机会调用之前传递给线程sem_wait。如果发生这种情况,信号处理程序将被调用并返回,然后sem_wait将被调用并永远阻塞。在这个测试程序中,你可以通过增加sleepin的长度来任意main地避免这种情况,但是没有办法让它不可能。这是无法解决的原因。

    有系统调用是少数原子疏通信号,而睡觉,然后返回到用户空间,如前再次阻止他们sigsuspendsigwaitinfopselect。这些是唯一可以避免这种竞争条件的系统调用。

    必须处理信号的多线程程序的最佳实践是让一个线程专门用于信号处理。为了使这项工作可靠,你应该阻止除同步CPU异常的所有信号(SIGABRTSIGBUSSIGFPESIGILLSIGSEGVSIGSYS,和SIGTRAP在最开始的)main之前创建的线程。然后,您为要处理的信号设置了一个什么都不做的信号处理程序(with SA_RESTART);这些实际上永远不会被调用,它们的目的是防止内核由于默认操作SIGUSR1或其他原因而终止进程。您关心的信号集必须包括用户中断的所有信号:, ,SIGHUP, SIGINT,SIGPWRSIGQUITSIGTERM, SIGTSTP, SIGXCPU, SIGXFSZ. 最后,创建信号处理线程,哪些循环调用sigwaitinfo的信号,并发送信息的适当设置为使用管道或条件变量或任何线程的休息,但信号真的。此线程绝不能阻塞在任何系统调用中sigwaitinfo.

    在这个测试程序的情况下,信号处理线程将SIGUSR1通过调用响应sem_post(&sem)。这将唤醒侦听器线程,或者它会导致侦听器线程不会首先被阻塞sem_wait


Dav*_*rtz 4

在多线程程序中,raise向调用 的线程发送信号raise。您需要使用kill(getpid(), ...)pthread_signal(thread, ...)

  • @alphabetical98332 如果你的手册页这么说,那是错误的。您可以在网上找到它并给我一个链接,以便我可以找出与谁联系来修复它吗?(我不确定为什么您认为看到“Insideighandler”证明了哪个线程收到了信号。在您的平台上,“signal”可能为所有未显式覆盖它的线程设置进程信号处理程序。但这只是一个猜测因为“信号”的行为在此上下文中未定义并且可能会有所不同。) (2认同)