[英]Why does sem_wait not unblock (and return -1) on an interrupt?
我有一个使用sem_wait
的程序。 Posix 规范说:
sem_wait()
function 可以通过传递信号来中断。
此外,在关于错误的部分中它说:
[EINTR] - 一个信号中断了这个 function。
但是,在我的程序中,发送信号不会解除对调用的阻塞(并返回-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;
}
程序输出Inside sighandler
然后挂起。
这里还有另一个问题,但它并没有真正提供任何明确性。
我是否误解了规范所说的内容? 仅供参考,我的电脑使用 Ubuntu GLIBC 2.31-0ubuntu9。
该程序无法按预期运行的原因有三个,其中只有两个是可以修复的。
正如大卫施瓦茨的回答中指出的那样,在多线程程序中, raise
向调用raise
的线程发送一个信号。
为了将信号发送到您想要的线程,在这个测试程序中,将raise(SIGUSR1)
更改为pthread_kill(thread, SIGUSR1)
。 但是,如果您希望该特定线程在发送到整个进程时处理SIGUSR1
,您需要做的是使用pthread_sigmask
在所有线程中阻止SIGUSR1
,除了应该处理它的线程。 (有关此内容的更多详细信息,请参见下文。)
在使用 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);
注意: memset(&sa, 0, sizeof sa)
不保证与sigemptyset(&sa.sa_mask)
具有相同的效果。
注意:信号处理程序是进程全局的,因此在哪个线程上调用sigaction
并不重要。 在几乎所有情况下,多线程程序都应该在创建任何线程之前在main
中执行所有sigaction
调用,以确保信号处理程序在任何信号发生之前处于活动状态。
信号可以在线程有机会调用sem_wait
之前传递给线程。 如果发生这种情况,信号处理程序将被调用并返回,然后sem_wait
将被调用,它将永远阻塞。 在这个测试程序中,你可以通过增加main
中的sleep
的长度来使这种情况变得不可能,但是没有办法让它变得不可能。 这是无法解决的原因。
有少量系统调用会在睡眠时自动解除对信号的阻塞,然后在返回用户空间之前再次阻塞它们,例如sigsuspend
、 sigwaitinfo
和pselect
。 这些是唯一可以避免这种竞争条件的系统调用。
必须处理信号的多线程程序的最佳实践是让一个线程专门用于信号处理。 为了使其可靠地工作,您应该在main
的最开始,在创建任何线程之前阻止所有信号,除了同步 CPU 异常( SIGABRT
、 SIGBUS
、 SIGFPE
、 SIGILL
、 SIGSEGV
、 SIGSYS
和SIGTRAP
)。 然后为要处理的信号设置一个无操作信号处理程序(使用SA_RESTART
); 这些实际上永远不会被调用,它们的目的是防止 kernel 由于SIGUSR1
的默认操作或其他原因而终止进程。 您关心的信号集必须包括用户中断的所有信号: SIGHUP
、 SIGINT
、 SIGPWR
、 SIGQUIT
、 SIGTERM
、 SIGTSTP
、 SIGXCPU
、 SIGXFSZ
。 最后,您创建信号处理线程,该线程循环调用sigwaitinfo
以获得适当的信号集,并使用管道或条件变量或除信号之外的任何其他东西将消息分派到线程的 rest。 该线程绝不能阻塞在sigwaitinfo
以外的任何系统调用中。
在这个测试程序的情况下,信号处理线程将通过调用sem_post(&sem)
来响应SIGUSR1
。 这将唤醒侦听器线程,或者它会导致侦听器线程首先不会在sem_wait
上被阻塞。
在多线程程序中, raise
向调用raise
的线程发送一个信号。 您需要使用kill(getpid(), ...)
或pthread_signal(thread, ...)
。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.