使用异步信号保证互斥安全

nne*_*neo 5 c synchronization posix signals

首先,我知道互斥锁通常不被视为异步安全.这个问题涉及在sigprocmask具有异步信号和信号处理程序的多线程程序中使互斥锁安全使用.

我在概念上有一些代码如下:

struct { int a, b; } gvars;

void sigfoo_handler(int signo, siginfo_t *info, void *context) {
    if(gvars.a == 42 || gvars.b == 13) {
        /* run a chained signal handler */
    }
}

/* called from normal code */
void update_gvars(int a, int b) {
    gvars.a = a;
    gvars.b = b;
}
Run Code Online (Sandbox Code Playgroud)

gvars是一个全局变量,它太大而不适合单个变量sig_atomic_t.它由普通代码更新并从信号处理程序读取.受控代码是链式信号处理程序,因此它必须在信号处理程序上下文中运行(它可以使用infocontext).因此,gvars必须通过某种同步机制来控制所有访问.使问题复杂化的是,程序是多线程的,并且任何线程都可以接收到SIGFOO.

问题:通过组合sigprocmask(或pthread_sigmask)pthread_mutex_t,可以保证同步,使用如下代码吗?

struct { int a, b; } gvars;
pthread_mutex_t gvars_mutex;

void sigfoo_handler(int signo, siginfo_t *info, void *context) {
    /* Assume SIGFOO's handler does not have NODEFER set, i.e. it is automatically blocked upon entry */
    pthread_mutex_lock(&gvars_mutex);
    int cond = gvars.a == 42 || gvars.b == 13;
    pthread_mutex_unlock(&gvars_mutex);

    if(cond) {
        /* run a chained signal handler */
    }
}

/* called from normal code */
void update_gvars(int a, int b) {
    sigset_t set, oset;
    sigemptyset(&set);
    sigaddset(&set, SIGFOO);
    pthread_sigmask(SIG_BLOCK, &set, &oset);
    pthread_mutex_lock(&gvars_mutex);
    gvars.a = a;
    gvars.b = b;
    pthread_mutex_unlock(&gvars_mutex);
    pthread_sigmask(SIG_SETMASK, &oset, NULL);
}
Run Code Online (Sandbox Code Playgroud)

逻辑如下:内部sigfoo_handler,SIGFOO被阻止,因此无法中断pthread_mutex_lock.其中update_gvars,SIGFOO在受pthread_sigmask保护的关键区域内不能在当前线程中引发,因此它也不能中断pthread_mutex_lock.假设没有其他信号(我们总是可以阻止任何其他可能有问题的信号),锁定/解锁应该始终以正常的,不间断的方式在当前线程上进行,并且使用锁定/解锁应该确保其他线程不要干涉.我是对的,还是应该避免这种做法?

Art*_*Art 1

通过提到 sig_atomic_t,您显然知道自己进入了未定义的行为领域。话虽这么说,我能看到这个确切示例在现代类 UNIX 系统上不起作用的唯一方法是信号是否使用 SA_NODEFER 设置。

互斥体足以确保不同线程之间的正确同步(包括在另一个线程中运行的信号处理程序),并且 sigmask 将防止该线程中的信号处理程序递归互斥体。

话虽这么说,你已经陷入了深水区,信号处理程序内有锁。一个信号处理程序可能足够安全,但如果有两个信号处理程序使用不同的锁执行相同的操作,最终会导致锁排序死锁。通过应用进程 sigmask 而不是线程 sigmask 可以在一定程度上缓解这种情况。例如,信号处理程序中的简单调试 fprintf 肯定会违反锁定顺序。

我会放弃并重新设计我的应用程序,因为信号处理程序中类似的东西表明它变得太复杂且太容易破坏。接触 sig_atomic_t 的信号处理程序是 C 标准中唯一定义的东西,因为正确处理其他事情的复杂性会急剧增加。