代码在优化时表现出不同的行为
Code shows different behaviour when optimized
总结
我尝试编写一个 Monte Carlo 模拟,该模拟最多可分叉到多个核心进程。一定时间后,parent 向所有 children 发送 SIGUSR1,然后应停止计算发送结果返回给 parent。
当我在没有任何优化的情况下编译 (clang thread_stop.c
) 时,行为符合预期。当我尝试优化代码 (clang -O1 thread_stop.c
) 时,信号被捕获,但 children 不会停止。
代码
我将代码缩减为行为相同的最小部分:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
#include <sys/types.h> /* pid_t */
#include <sys/mman.h> /* mmap */
#define MAX 1 /* Max time to run */
static int a=0; /* int to be changed when signal arrives */
void sig_handler(int signo) {
if (signo == SIGUSR1){
a=1;
printf("signal caught\n");
}
}
int main(void){
int * comm;
pid_t pid;
/* map to allow child processes access same array */
comm = mmap(NULL, sizeof(int), PROT_READ | PROT_WRITE,
MAP_SHARED | MAP_ANONYMOUS, -1, 0);
*comm = 0;
pid=fork();
if(pid == 0){ /* child process */
signal(SIGUSR1, sig_handler); /* catch signal */
do {
/* do things */
} while(a == 0);
printf("Child exit(0)\n");
*comm = 2;
exit(0); /* exit for child process */
} /* if(pid == 0) - code below is parent only */
printf("Started child process, sleeping %d seconds\n", MAX);
sleep(MAX);
printf("Send signal to child\n");
kill(pid, SIGUSR1); /* send SIGUSR1 */
while(*comm != 2) usleep(10000);
printf("Child process ended\n");
/* clean up */
munmap(comm, sizeof(int));
return 0;
}
系统
clang 在 termux (clang 9.0.1) 和 lubuntu (clang 6.0.0-lubuntu2) 上显示了这一点。
在异步调用的信号处理程序中可以执行的操作存在限制。在您的代码中,发生这种情况是因为 kill
是从一个单独的进程调用的。
在 ISO C 中,唯一允许的可观察操作是修改 sig_atomic_t
类型的变量。
In POSIX there is a bit more leniency:
the behavior is undefined if the signal handler refers to any object other than errno with static storage duration other than by assigning a value to an object declared as volatile sig_atomic_t, or if the signal handler calls any function defined in this standard other than one of the functions listed in the following table.
The following table defines a set of functions that shall be async-signal-safe. Therefore, applications can call them, without restriction, from signal-catching functions. Note that, although there is no restriction on the calls themselves, for certain functions there are restrictions on subsequent behavior after the function is called from a signal-catching function (see longjmp).
printf
函数不在 table 中,因此您的程序在执行信号时会导致未定义的行为(这意味着可能会出现意外结果)。
因此您需要停止在信号处理程序中调用 printf
,并将 a
更改为类型 volatile sig_atomic_t
。
内存位置 *comm
上也存在竞争条件。一个线程读取它,而另一个线程可能同时写入它,没有同步。但是,我无法在 POSIX 文档中找到这样做的后果。
改成volatile sig_atomic_t
治愈。感谢您的快速帮助。
总结
我尝试编写一个 Monte Carlo 模拟,该模拟最多可分叉到多个核心进程。一定时间后,parent 向所有 children 发送 SIGUSR1,然后应停止计算发送结果返回给 parent。
当我在没有任何优化的情况下编译 (clang thread_stop.c
) 时,行为符合预期。当我尝试优化代码 (clang -O1 thread_stop.c
) 时,信号被捕获,但 children 不会停止。
代码
我将代码缩减为行为相同的最小部分:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
#include <sys/types.h> /* pid_t */
#include <sys/mman.h> /* mmap */
#define MAX 1 /* Max time to run */
static int a=0; /* int to be changed when signal arrives */
void sig_handler(int signo) {
if (signo == SIGUSR1){
a=1;
printf("signal caught\n");
}
}
int main(void){
int * comm;
pid_t pid;
/* map to allow child processes access same array */
comm = mmap(NULL, sizeof(int), PROT_READ | PROT_WRITE,
MAP_SHARED | MAP_ANONYMOUS, -1, 0);
*comm = 0;
pid=fork();
if(pid == 0){ /* child process */
signal(SIGUSR1, sig_handler); /* catch signal */
do {
/* do things */
} while(a == 0);
printf("Child exit(0)\n");
*comm = 2;
exit(0); /* exit for child process */
} /* if(pid == 0) - code below is parent only */
printf("Started child process, sleeping %d seconds\n", MAX);
sleep(MAX);
printf("Send signal to child\n");
kill(pid, SIGUSR1); /* send SIGUSR1 */
while(*comm != 2) usleep(10000);
printf("Child process ended\n");
/* clean up */
munmap(comm, sizeof(int));
return 0;
}
系统
clang 在 termux (clang 9.0.1) 和 lubuntu (clang 6.0.0-lubuntu2) 上显示了这一点。
在异步调用的信号处理程序中可以执行的操作存在限制。在您的代码中,发生这种情况是因为 kill
是从一个单独的进程调用的。
在 ISO C 中,唯一允许的可观察操作是修改 sig_atomic_t
类型的变量。
In POSIX there is a bit more leniency:
the behavior is undefined if the signal handler refers to any object other than errno with static storage duration other than by assigning a value to an object declared as volatile sig_atomic_t, or if the signal handler calls any function defined in this standard other than one of the functions listed in the following table.
The following table defines a set of functions that shall be async-signal-safe. Therefore, applications can call them, without restriction, from signal-catching functions. Note that, although there is no restriction on the calls themselves, for certain functions there are restrictions on subsequent behavior after the function is called from a signal-catching function (see longjmp).
printf
函数不在 table 中,因此您的程序在执行信号时会导致未定义的行为(这意味着可能会出现意外结果)。
因此您需要停止在信号处理程序中调用 printf
,并将 a
更改为类型 volatile sig_atomic_t
。
内存位置 *comm
上也存在竞争条件。一个线程读取它,而另一个线程可能同时写入它,没有同步。但是,我无法在 POSIX 文档中找到这样做的后果。
改成volatile sig_atomic_t
治愈。感谢您的快速帮助。