具有长信号处理程序的计时器问题(SIGALARM)
有一个定时器,每 1 秒发出一次信号 SIGALARM。休眠的信号处理程序 2 秒已注册。会发生什么?具体来说,我有以下代码,其中进程运行多个线程。非常有趣的是,使用这个长信号处理程序,看起来其他线程被阻止执行......任何人都可以解释为什么会出现这种情况吗?
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h> //rand
#include <sys/wait.h>
#include <time.h>
#include <sys/time.h>
#include <pthread.h>
#define NUM_THREADS 2
int init_timer(int real_time, int msec) {
struct itimerval timeslice;
timeslice.it_interval.tv_sec = msec / 1000;
timeslice.it_interval.tv_usec = (msec % 1000) * 1000;
timeslice.it_value.tv_sec = msec / 1000;
timeslice.it_value.tv_usec = (msec % 1000) * 1000;
setitimer(real_time ? ITIMER_REAL : ITIMER_VIRTUAL, ×lice, NULL);
return 0;
}
void install_handler(int signo, void(*handler)(int)) {
sigset_t set;
struct sigaction act;
/* Setup the handler */
act.sa_handler = handler;
act.sa_flags = SA_RESTART;
sigaction(signo, &act, 0);
/* Unblock the signal */
sigemptyset(&set);
sigaddset(&set, signo);
sigprocmask(SIG_UNBLOCK, &set, NULL);
return;
}
void timerTest(int signo)
{
printf("000\n");
sleep(1);
printf("111\n");
}
void * threadTest(void * threadId)
{
while(true)
{
printf("222\n");
}
}
int main(int argc, char *argv[]) {
int real_time = 1;
int tick_msec = 10;
init_timer(real_time, tick_msec);
install_handler(real_time ? SIGALRM : SIGVTALRM, &timerTest);
pthread_t threads[NUM_THREADS];
int rc;
long t;
for (t = 0; t < NUM_THREADS; t++) {
rc = pthread_create(&threads[t], NULL, threadTest, (void *) t);
if (rc) {
exit(-1);
}
}
void * status;
for (t = 0; t < NUM_THREADS; t++) {
rc = pthread_join(threads[t], &status);
if (rc) {
exit(-1);
}
}
pthread_exit(NULL);
}
打印输出:
222
222
222
222
...
222
000
111
000
111
...
第一个111出现后就不会出现222了吗?为什么会这样?
There is a timer which sends out signal SIGALARM every 1 sec. A signal handler which sleeps
2 sec is registered. What happens? Specifically, I have following code, in which the process runs multiple threads. It's quite interesting that with this long signal handler, it looks other threads are blocked from execution... Can anyone explain why this is the case?
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h> //rand
#include <sys/wait.h>
#include <time.h>
#include <sys/time.h>
#include <pthread.h>
#define NUM_THREADS 2
int init_timer(int real_time, int msec) {
struct itimerval timeslice;
timeslice.it_interval.tv_sec = msec / 1000;
timeslice.it_interval.tv_usec = (msec % 1000) * 1000;
timeslice.it_value.tv_sec = msec / 1000;
timeslice.it_value.tv_usec = (msec % 1000) * 1000;
setitimer(real_time ? ITIMER_REAL : ITIMER_VIRTUAL, ×lice, NULL);
return 0;
}
void install_handler(int signo, void(*handler)(int)) {
sigset_t set;
struct sigaction act;
/* Setup the handler */
act.sa_handler = handler;
act.sa_flags = SA_RESTART;
sigaction(signo, &act, 0);
/* Unblock the signal */
sigemptyset(&set);
sigaddset(&set, signo);
sigprocmask(SIG_UNBLOCK, &set, NULL);
return;
}
void timerTest(int signo)
{
printf("000\n");
sleep(1);
printf("111\n");
}
void * threadTest(void * threadId)
{
while(true)
{
printf("222\n");
}
}
int main(int argc, char *argv[]) {
int real_time = 1;
int tick_msec = 10;
init_timer(real_time, tick_msec);
install_handler(real_time ? SIGALRM : SIGVTALRM, &timerTest);
pthread_t threads[NUM_THREADS];
int rc;
long t;
for (t = 0; t < NUM_THREADS; t++) {
rc = pthread_create(&threads[t], NULL, threadTest, (void *) t);
if (rc) {
exit(-1);
}
}
void * status;
for (t = 0; t < NUM_THREADS; t++) {
rc = pthread_join(threads[t], &status);
if (rc) {
exit(-1);
}
}
pthread_exit(NULL);
}
printout:
222
222
222
222
...
222
000
111
000
111
...
there will be no 222 after the first 111 occurs? why so?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(3)
信号被传递到特定线程,因此信号处理程序在特定线程(信号传递到的线程)中运行。如果信号传递到写出
222\n
的线程,则该线程必须停止写出222\n
并运行信号处理程序。您的示例信号处理程序需要一整秒才能运行,因此在这一整秒内该线程可能不会写出222\n
。此外,由于您使用
printf
写出所有这些字节,因此 libc 中会进行一些锁定。由于 printf 不是“异步信号安全”函数,因此实际上未定义在信号处理程序中使用它时会发生什么。对于您观察到的行为,一种可能的解释是这样的。如果信号被传递到一个线程,而该线程持有 stdout 锁,则在处理程序返回并且该线程中运行的“正常”代码可以释放该锁之前,其他线程将无法写入 stdout。不过,在这种情况下,信号处理程序仍然可以写入标准输出,因为锁是一个可以重复获取任何特定线程的rlock。不过,这可能会有所不同,具体取决于特定平台、C 库、线程库或月相。不过,您的示例很容易转换为使用 write(2) ,它演示了或多或少相同的问题行为,具有或多或少相同的修复,并且不依赖于未定义的行为。如果您在
222\n
线程中SIG_BLOCK
计时器信号,那么信号处理程序将始终在主线程中运行,并且您将继续获取222\n
信号处理程序休眠时输出。Seth 还强调了仅在信号处理程序中使用安全函数的观点。使用任何其他意味着您的程序的行为是未定义的。
The signal is delivered to a particular thread, so the signal handler runs in a particular thread (the thread the signal was delivered to). If the signal is delivered to a thread writing out
222\n
, then that thread must stop writing out222\n
and run the signal handler. Your example signal handler takes a full second to run, so that's a full second during which that thread may not write out222\n
.Additionally, since you are using
printf
to write out all of these bytes, there is some locking being done in libc. Sinceprintf
is not an "async signal safe" function, it's actually undefined what happens if you use it in a signal handler. One possible explanation for the behavior you observe is this. If the signal is delivered to a thread while that thread holds the stdout lock, then no other thread will be able to write to stdout until the handler returns and the lock can be released by the "normal" code running in that thread. The signal handler can still write to stdout in this case, though, because the lock is an rlock which can be acquired repeatedly any particular thread. This may vary from depending on the specific platform, C library, thread library, or phase of the moon, though. Your example is easily converted to use write(2) though, which demonstrates more or less the same problem behavior, has more or less the same fix, and doesn't rely on undefined behavior.If you
SIG_BLOCK
the timer signal in the222\n
threads, then the signal handler will always run in the main thread and you will continue to get222\n
output while the signal handler sleeps.Seth also makes a great point about only using the safe functions in signal handlers. Using any others means your program's behavior is undefined.
通常,当您处于信号处理程序中时,信号(或至少该信号)会被阻止。在信号处理程序中做太多事情也是一个坏主意。一般来说,您应该设置一个变量或类似的东西,然后在正常的代码路径中处理信号。
有关允许或拒绝在信号处理程序内接收信号的方法,请参阅 sigaction 的 SA_NODEFER 标志。
从信号处理程序内部安全调用的函数数量也有限。 signal(7) 手册页对此进行了描述。 “信号处理函数必须非常小心,因为其他地方的处理可能会在程序执行过程中的任意点被中断。POSIX 有“安全函数”的概念。如果信号中断了不安全函数的执行,则处理函数调用不安全函数,则程序的行为未定义”
从信号处理程序内部调用不安全函数的程序已损坏。在某些机器上它会
“工作”;在其他设备上它会核心转储。允许做任何事或
没有什么,包括重新格式化磁盘,让用户
向后播放有沙沙声的 Barry Manilow 唱片,或者掉下一个 tacnuke
在达拉斯。尝试调用不安全的函数会将程序置于未定义行为的灰色地带。
向 der Mouse 致歉。
Often times, signals (or at least that signal) is blocked when you are in a signal handler. It is also a bad idea to do very much in a signal handler. Generally you should set a variable or something like that, and then deal with the signal once you are in your normal codepath.
See sigaction's SA_NODEFER flag for a way to permit or deny receiving a signal inside a signal handler.
There is also a limited number of functions which are safe to call from inside a signal handler. signal(7) man page describes this. "A signal handler function must be very careful, since processing elsewhere may be interrupted at some arbitrary point in the execution of the program. POSIX has the concept of "safe function". If a signal interrupts the execution of an unsafe function, and handler calls an unsafe function, then the behavior of the program is undefined"
A program which calls an unsafe function from inside a signal handler is broken. On some machines it will
"work"; on others it will coredump. It is permitted to do anything or
nothing, including reformatting the disk, subjecting the user to
scratchy Barry Manilow records played backwards, or dropping a tacnuke
on Dallas. Attempting to call an unsafe function puts the program into the twilight zone of undefined behavior.
With apologies to der Mouse.
在信号处理程序中使用 printf 是不正确的。
关于将要处理 SIGALARM 的线程,您可能会找到一些有关信号阻塞的有用信息 这里。这篇文章包含有启发性的 Linux 内核代码。本质上,如果主线程想要接收信号,则由主线程处理该信号。如果没有,它将由任何其他需要它的线程处理。您可以使用
pthread_sigmask(3)
屏蔽线程中的特定信号It's incorrect to use
printf
in a signal handler.Regarding the thread that is going to handle the SIGALARM you may find some useful information on signal blocking here. The post contains enlightening Linux kernel code. Essentially the signal is handled by the main thread if it wants to receive it. If not, it will be handled by any other thread that wants it. You could mask specific signals in a thread using
pthread_sigmask(3)