使用互斥锁等待退出信号

6tr1vspr  于 2022-10-23  发布在  Linux
关注(0)|答案(3)|浏览(159)

我希望进程在收到退出信号时退出。所以主线程需要等待退出信号,我以前是这样写的。

int signal(int a) {
    exit_flag = 0;
}
//wait in main thread
while(exit_flag){
    sleep(1);
}

但它的React有点慢。所以我找到了一个像这样的新方法,

//signal function
int signal(int a) {
    pthread_mutex_unlock(&mutex);
}
//main thread

pthread_mutex_lock(&lock);
pthread_mutex_lock(&lock);

我不确定这是不是正确的。我的问题是,如果这是一个合适的方式来等待出口信号,或者你可以告诉我一个更好的方法?
衷心感谢大家的回复。我知道在信号处理程序中使用互斥锁是未定义的,并且不安全。但是,如果我通过其他方法(如jrpc调用或类似的方法)发送退出消息,上面的代码是否正确?

5uzkadbs

5uzkadbs1#

我的问题是,如果这是一个合适的方式来等待出口信号,或者你可以告诉我一个更好的方法?
不,是因为@AndrewHenle在his answer中描述得很好的原因。
有多种正确的方法可以做到这一点,但其中许多方法重新发明了pause()函数及其改进的替代方法sigsuspend()。这两个是专门用于等待信号传递的。Glibc手册包含关于how to use them for the purpose的一节。
或者,如果您的进程是多线程的,并且您只想将一个线程用于等待,那么可以使用sigwait()sigwaitinfo() and sigtimedwait()

让整个流程等待

假设您希望整个进程停止,直到将SIGUSR1传递给它,然后退出。在安装信号处理程序之后,您可能会使用如下内容:

// sufficient for this case even in a multi-threaded program:
volatile sig_atomic_t exit_flag;

// ...

/*
 * Wait, if necessary, until a SIGUSR1 is received, then exit with status 0.
 *
 * Assumes that a signal handler for SIGUSR1 is already installed, that the
 * handler will set variable `exit_flag` to nonzero when it runs, and that
 * nothing else will modify exit_flag incompatibly.
 */
void wait_to_exit(void) {
    sigset_t temp_mask, mask;

    sigemptyset(&temp_mask);
    sigaddset(&temp_mask, SIGUSR1);

    /*
     * Temporarily block the signal we plan to wait for, to ensure that we
     * don't miss a signal.
     */
    sigprocmask(SIG_BLOCK, &temp_mask, &mask);

    // Prepare to wait for the expected signal even if it is presently blocked
    sigdelset(&mask, SIGUSR1);

    // if we haven't already received the signal, then block the whole process until we do
    while (!exit_flag) {
        sigsuspend(&mask);
    }

    // No need to reset any signal masks because we're about to ...
    exit(0);
}

关于标志的数据类型

对上面代码中的一个注解进行扩展,即使在多线程程序中,volatile sig_atomic_t也是足够的类型。sigsuspend()被指定为在信号处理程序返回之后返回,并且信号处理程序直到实际发生对标志的写入之后才返回(由于易失性)。然后,由于易失性的原因,调用sigSuspend的线程必须读取处理程序写入的值,或随后写入同一变量的其他值。
然而,volatile通常不足以保证线程安全,因此即使在这种情况下不是必需的,您也可以考虑通过使用atomic_flag(在stdatomic.h中声明)来回避问题和任何不确定性;这需要支持C11或更高版本。

只有一个线程等待

对于多个线程中有一个在等待信号的情况,它的结构应该有很大的不同。在这种情况下,您不需要信号处理程序或标志,但应该通过sigprocmask()为所有线程*阻止预期的信号:

sigset_t mask;

    sigemptyset(&mask);
    sigaddset(&mask, SIGUSR1);

    sigprocmask(SIG_BLOCK, &mask, NULL);

这将防止在任何线程中执行预期信号的默认(或自定义)处置,并确保要等待的线程以外的线程不会使用该信号。通常,它应该在程序执行的非常早的时候完成。
然后,为了等待信号,一个线程执行以下操作:

void wait_to_exit(void) {
    sigset_t mask;
    int sig;

    sigemptyset(&temp_mask);
    sigaddset(&temp_mask, SIGUSR1);

    // if we haven't already received the signal, then block this thread until we do
    if (sigwait(&mask, &sig) != 0) {
        // Something is terribly wrong
        fputs("sigwait failed\n", stderr);
        abort();
    }
    assert(sig == SIGUSR1);

    // Terminate the process (all threads)
    exit(0);
}

如果你指的是一般的“信号”

如果您的意思是“信号”作为“同步”通知的通用术语,而不是C信号处理工具的练习,那么@AndrewHenle关于信号量的建议将是完美的。在这种情况下,一定要接受这个答案。

8ehkhllq

8ehkhllq2#

不,这不是**正确的。
首先,pthread_mutex_unlock()不是一个异步信号安全函数,不能从信号处理程序中安全地调用。
其次,互斥锁由线程锁定。如果信号处理程序在与锁定互斥锁的线程不同的线程中运行,则它将can not unlock the mutex
如果线程试图解锁尚未锁定的互斥体或解锁的互斥体,则pthread_mutex_unlock()的行为应如下表的Unlock When Not Owner列中所述。
该表中唯一的条目是“未定义的行为”和“返回的错误”。并且您无法真正控制信号将被传递到哪个线程(至少在不编写复杂的信号处理代码的情况下是如此)。
第三,此代码

pthread_mutex_lock(&lock);
pthread_mutex_lock(&lock);

per that same table将不会安全地阻止任何类型的互斥体。该代码将死锁,继续锁定互斥锁,或者调用未定义的行为,这些行为甚至可能看起来“工作”,但会使您的程序处于未知状态,这可能会在以后导致错误。
编辑:
第四,如果信号被多次传递,对pthread_mutex_unlock()的多次调用将再次导致错误或未定义的行为。
但有一种异步信号安全的方法可以阻止等待信号:sem_wait()
sem_post()异步信号安全的,可以在信号处理器内部安全地调用,也可以安全地多次调用--多次调用sem_post()只允许相应数量的sem_wait()获得信号量,但只需要一次就可以工作:

//signal function
int signal(int a) {
    sem_post(&sem);
}
//main thread

sem_wait(&sem);

请注意,在信号处理程序中调用sem_wait()是**不安全的。

fgw7neuy

fgw7neuy3#

除了接受信号(通过sigwaitsigtimedwait),我还喜欢古老的self-pipe trick“维护管道并选择管道输入的可读性。在[Signal]处理程序中,向管道输出写入一个字节(非阻塞,以防万一)。
我还想补充一点,信号处理程序应该由sigaction作为SA_RESTARTABLE安装。
现在,您可以安全地将信号传递与IO混合(例如,通过selectpoll,或者仅阻塞read,直到该字节到达)。

相关问题