静态初始化不安全调用的线程安全

Posted

技术标签:

【中文标题】静态初始化不安全调用的线程安全【英文标题】:Thread safety of static initialized unsafe call 【发布时间】:2016-12-23 03:10:07 【问题描述】:

在图书馆的某个地方,我有一个看起来像的函数 -

inline int getIword()

    static int i = std::ios_base::xalloc();
    return i;

现在您可以阅读有关std::ios_base::xalloc() 调用here 的信息,但我想从提及的链接中强调这一行 -

这个函数是线程安全的;多个线程的并发访问不会导致数据竞争。 (C++14 起)

上面写着“自 C++14 起”,但我也需要 C++11 支持。由于函数调用实际上是在初始化 getIword() 方法的静态局部变量,并且我们知道 local static variable initialization is thread safe 用于 C++11 代码,因此假设此代码是安全的 -

如果仅对函数进行后续读取调用,则安全,例如。 auto something = getIword().

安全吗?如果代码如下所示:

...

operator<<(std::ostream &os, T const value)

    if (value == ...) 
        os.iword(getIword()) = 1;
     else if (value == ...) 
        os.iword(getIword()) = 0;
    
    return os;

...

如果在后面的示例中不安全,我应该将 lock_guards 放在哪里以使其对 C++11 安全?围绕return i 或整个方法或在哪里进行调用?

【问题讨论】:

检查您的 C++ 实现是否已经是线程安全的可能是值得的。 保证在 C++14 中是新的,但对于实际实现来说很常见,可以尽早提供这些细节。 @MSalters 关于线程安全静态初始化的保证可以追溯到 C++11。 @NathanOliver MSalters 可能在谈论该功能的保证。 【参考方案1】:

静态局部变量初始化是线程安全的,因为如果多个线程调用该函数,那么只有其中一个线程会实际初始化变量。它不保护你初始化的东西。这意味着在这种情况下,如果您有两个不同的线程,一个调用 getIword,另一个调用另一个恰好同时调用 std::ios_base::xalloc() 的函数,那么这两个调用将不会同步,您将拥有一个数据比赛反过来又是未定义的行为。

【讨论】:

【参考方案2】:

在我的电脑上,我可以看到std::ios_base::xalloc() 的实现是:

    static int __CLRCALL_OR_CDECL xalloc()
       // allocate new iword/pword index
    _BEGIN_LOCK(_LOCK_STREAM)   // lock thread to ensure atomicity
        return (_Index++);
    _END_LOCK()
    

所以基本上它只是做一个增量值,没有线程安全保证你会在竞争条件下从std::ios_base::xalloc() 中得到一些重复值。

我会采取的解决方案是使用std::atomic 编写您自己的xalloc()

int xalloc()

    static std::atomic_int i;
    return i++;

或者将std::ios_base::xalloc() 包装成一个带锁的新方法。

【讨论】:

以上是关于静态初始化不安全调用的线程安全的主要内容,如果未能解决你的问题,请参考以下文章

可重入和线程安全

局部静态的线程安全初始化:MSVC [重复]

java线程安全问题之静态变量实例变量局部变量

java线程安全问题之静态变量实例变量局部变量

java线程安全问题之静态变量实例变量局部变量

java线程安全问题之静态变量实例变量局部变量