我知道在信号处理程序中默认使用条件变量是不安全的(c++11 use condition variable in signal handler),但是,根据https://man7.org/linux/man-pages/man7/signal-safety.7.html,它似乎暗示如果我们确保在此期间没有其他信号被处理,那么这样做可能是可以的。
1.当调用不安全的函数或对信号处理程序也访问的全局数据进行操作时,阻止主程序中的信号传递。
那么,如果我使用std::atomic_bool来确保任何时候只有一个处理在运行,那么使用条件变量是否安全呢?类似于下面的代码(简化了很多):
std::atomic_bool g_signalBlock; // global
void handleSignal(int signalNumber, siginfo_t* signalInfo, void* ucontext) {
if (g_signalBlock.exchange(true)) {
return; // ignore this signal. another signal being processed.
} else {
// process the signal
if (signalNumber == SIGUSR2) {
// notify another thread using std::condition_variable
} else {
// re-raise non-SIGUSR2 signal (to exit the program)
signal(signalNumber, SIG_DFL);
raise(signal);
}
g_signalBlock.store(false); // now allow other signals
}
}
使用案例:
作为信号处理逻辑的一部分,存在当前异步信号安全但耗时太长的某些操作(记录到以异步信号安全方式实现的文件)。这对于诸如SIGABRT等的信号是可接受的,其中处理将退出,但同一处理程序还用于处理SIGUSR2信号以记录线程跟踪(在某些条件下自动触发)。我想避免它影响其他线程,因此需要将日志记录委托给后台线程。
3条答案
按热度按时间zazmityj1#
不,这不是异步安全的。
好吧,除了这个特定的执行线程,其他的执行线程也可能使用这个
std::condition_variable
。您提出的逻辑足以保证此信号处理程序 * 中的异步安全性 *。
但探戈需要两个人。
如果这个条件变量只在这里使用,而不在其他地方使用,那么它就是异步安全的,但是,当然,事实并非如此,一个条件变量只在这里被访问是毫无意义的,它必须在其他地方被访问,游戏结束了。
让我们慢慢重读这一部分:
[当]对信号处理程序也访问的全局数据进行操作时,阻止信号传递。
不仅这个块必须在信号处理程序本身中,而且任何时候,任何其他执行线程甚至在与这个条件变量相同的方向上打喷嚏,信号处理程序都必须被阻止访问它,这就是它所说的。
这是在告诉你:无论何时,当你在任何执行线程中有东西访问全局数据时,你必须阻止信号处理程序访问它。2是的,这意味着条件变量。3每次你从另一个执行线程访问它时,你必须阻止信号。
信号处理程序本身的阻塞只是"全局数据"已经被"信号处理程序访问"的一种特殊情况,它已经被访问了--由信号处理程序的第一次初始调用。
qhhrdooz2#
仅仅确保一次只能运行一个信号处理程序是不够的,信号处理程序可以与程序中的任何线程竞争。
因此,如果您有一个条件变量
cv
,并且您从信号处理程序中访问它,则必须确保当任何其他访问cv
的代码正在运行时,不能调用该信号处理程序。(* 例如 * 忽略该信号或终止该过程)或在执行访问cv
的代码之前屏蔽掉该信号(* 例如 * 使用sigprocmask
)。你可能会觉得很奇怪。如果两个线程可以并发访问
cv
,那么为什么一个线程和一个信号处理程序不能安全地并发访问cv
呢?所有线程安全类型都需要防止第二个线程在第一个线程正在处理某件事的时候试图对它们做某件事。但是如果一个线程正在处理变量的某件事,然后该线程被中断,以便执行信号处理程序(在 * 同一个线程 * 上执行),该处理程序试图对同一个变量执行某些操作,则该实现不一定能够正确地处理这种相同线程的重入。只有无锁原子才能保证单个操作从硬件的Angular 来看确实是不可分割的,因此,线程在访问变量的过程中不可能被中断以运行信号处理程序。
如果你希望信号处理程序向队列中添加一些东西,然后通知条件变量,让某个线程知道队列非空,不幸的是,标准不允许这样做。信号处理程序应该只使用无锁原子变量与程序的其余部分通信。自C20以来,原子变量本身支持
wait
,notify_one
和notify_all
操作,所以你可以使用它们。在C20之前,选项不是很好;你可以(1)使用平台特定的功能,比如futexes,(2)使用信号安全的库函数进行通信;例如,信号处理程序可以将write
转换为文件描述符,这可以唤醒另一个阻塞在read
上的线程;或者(3)具有仅旋转直到原子变量的值改变的线程。pb3s4cty3#
根据cppreference,可以在信号处理程序中使用STL的原子,只要它们是无锁的。
参见https://en.cppreference.com/w/cpp/utility/program/signal
但是你不能用
std::condition_variable
通知线程,它被直接写为信号处理程序中不安全的操作,此外,应用notify_one()
而不首先锁定和解锁其关联的互斥锁是一个坏主意。相反,您可以使用更简单的等待方案:一个线程每隔10ms检查一次原子变量的状态,信号处理器修改原子变量并执行所有必要的更新和通知。这些有10ms的延迟,但这应该不是一个大问题。
有atomic的
notify_one
,它在C++20中可用,尽管不完全确定它在信号上是否安全。