我们假设我有这个功能:
int my_thread_id(){
static int counter {0};
thread_local int tid{++counter};
return tid;
}
即使在第一次通话时,此功能(my_thread_id)是否同步信号安全?
答案 0 :(得分:3)
没有
信号处理程序没有关于它们正在执行哪个线程的概念,因此thread_local
在那里没有有效的语义。 [intro.multithread] P2:
由于对
raise
函数的调用而执行的信号处理程序属于与调用raise
函数相同的执行线程。否则,未指定哪个执行线程包含信号处理程序调用。
同样相关的是p23:
如果
,则可能会并发两个操作
- 它们由不同的线程执行,或
- 它们没有排序,至少有一个是由信号处理程序执行的。
程序的执行包含数据竞争,如果它包含两个可能同时发生冲突的动作,其中至少有一个不是原子动作,并且在其他动作之前都不会发生,除了特殊情况信号处理程序如下所述。任何此类数据竞争都会导致未定义的行为。
(所引用的信号处理程序的特殊情况仅与类型volatile sig_atomic_t
有关,在此处不适用。)
由于[intro.execution] p6:
,第二个子弹属于如果由于调用
raise
函数而执行信号处理程序,则在调用raise
函数之后和返回之前对处理程序的执行进行排序。 [注意:当由于其他原因收到信号时,信号处理程序的执行通常与程序的其余部分无关。 - 结束记录]
答案 1 :(得分:1)
这个答案可以被视为ildjarn给出的答案的附录。严格说来C ++ 14标准时,使用来自信号处理程序的thread_local存储数据会导致未定义的行为。
但是,在某些平台上,这种使用可能是允许的。例如,大多数POSIX系统通过使用按线程分配的特殊数据段(如堆栈)来实现线程本地存储。有关详细说明,请参阅this document。在这种情况下,访问线程本地数据是异步信号安全的,因为它不涉及任何锁定。但是,除非仅使用std::atomic_signal_fence
访问原子或限制访问,否则信号处理程序读取或写入的数据可能仍然不一致。这样做的原因是编译器现在知道信号处理程序何时可能中断执行,从而可能重新排序读写指令。 std::atomic_signal_fence
禁止这种重新排序和CPU的重新排序不是问题,因为执行发生在同一个线程中,只有当结果(在线程内)与指令有相同时才允许CPU重新排序指令已按顺序执行。
除了std :: atomic_signal_fence之外,使用std :: atomic类型的变量是安全的,只要它们是无锁的(如std :: is_lock_free所示)。
在Linux上(我相信大多数其他POSIX平台),是否将信号分派到特定线程的问题取决于该信号的生成方式以及信号的确切类型。例如,始终将SIGSEGV
和SIGBUS
分派给导致错误的线程,从而产生信号。在这种情况下,使用线程本地存储可以是从这些错误中恢复的便捷方式。但是,在保持代码可移植到支持C ++标准的所有平台的同时,无法做到这一点。