静态函数范围对象的构造是线程安全的吗?

Posted

技术标签:

【中文标题】静态函数范围对象的构造是线程安全的吗?【英文标题】:Is construction of static function-scope object thread-safe? 【发布时间】:2010-12-30 22:23:26 【问题描述】:

假设我有一个函数尝试使用此代码保护全局计数器:

 static MyCriticalSectionWrapper lock;
 lock.Enter();
 counter = ++m_counter;
 lock.Leave();

两个线程是否有可能调用lock 的构造函数?实现这一目标的安全方法是什么?

【问题讨论】:

另见***.com/questions/55510/… 【参考方案1】:

锁对象本身的创建不是线程安全的。根据编译器的不同,如果多个线程(几乎)同时进入函数,您可能会创建多个独立的锁对象。

这个问题的解决方法是使用:

操作系统保证一次性初始化(针对锁对象) Double-checked locking(假设它对您的特定情况是安全的) 锁对象的线程安全单例 对于您的具体示例,您可以使用线程安全互锁(例如,Windows 的 InterlockedIncrement() 函数)操作来进行增量并避免完全锁定

【讨论】:

然后,为了安全起见,我需要将它包装在一个关键部分,导致同样的问题......对吧? 一个线程安全的单例比你想象的更难写。更好的解决方案是消除全局并使用活动对象。然后,活动对象将负责您打算对全局执行的任何操作。 AFAIK,双重检查锁定不被认为是线程安全的。这是一种反模式。 有时您需要一个单身人士来分配唯一的 ID。实施起来应该没有那么难。 @AlexEmelianov 是的,很难正确地实现。如果您为了简单而引入单例,那么解决方案就会失败。【参考方案2】:

构造函数调用可以依赖于实现和/或执行环境,但这不是 scoped_lock 所以不是问题。

我认为主要操作被适当地防止了多线程访问。

(你知道,全局对全局,函数静态对函数静态。该锁变量必须与受保护对象在同一范围内定义。)

【讨论】:

"锁变量必须与被保护对象定义在同一范围内。" - 这是一个很好的经验法则。谢谢。【参考方案3】:

原始示例代码:

 static MyCriticalSectionWrapper lock;
 lock.Enter();
 counter = ++m_counter;
 lock.Leave();

我意识到计数器代码可能只是一个占位符,但是如果它实际上是您尝试做的,您可以使用 Windows 函数“InterlockedIncrement()”来完成此操作。 示例:

 // atomic increment for thread safety
 InterlockedIncrement(&m_counter);
 counter = m_counter;

【讨论】:

或最近的 std::atomic【参考方案4】:

这取决于你的锁实现。

【讨论】:

@wilhelmtell:对不起,我不清楚。这里,lock 是一个静态块范围变量。 (当前)标准的第 [6.7] 节第 4 段说“这样的对象在控制第一次通过其声明时被初始化”。 ***.com/questions/898432/… 涵盖了 C++ 编译器是如何做到这一点的。它不是线程安全的,因此多个线程可以同时运行MyCriticalSectionWrapper 构造函数,使其处于未定义状态。不过,对于基于最新草案 n3126 的 C++0x,您的说法是正确的。 @wilhelmtell:“MyCriticalSectionWrapper”是一个 C++ 类。即使内部编写正确,两个不同的实例也不具有与一个实例相同的行为。创建两个不同实例的两个线程将(正确但毫无意义地)锁定两个不同的上下文。我的问题是“静态”声明是否以某种方式阻止了双重初始化。 @wilhelmtell:请不要以为我不懂基础知识。您可能会对我调试了多少并发代码感到惊讶……此时,我只需要证明某些现有代码并不像拥有它的团队所认为的那样安全。使用建设性的语言而不是“先读书”之类的评论总是有帮助的。 @wilhelmtell lock 不是锁对象,而是临界区实例本身。这是一个不幸的命名,因为 lock 应该被命名为 sync_object 或类似的名称。其他人担心的是,关键部分或互斥对象在首先正确创建之前无法锁定。 显然 C++0x 将通过静态局部变量保证线程安全,正如 Daniel 的链接所暗示的:***.com/questions/898432/…。 C++2003 中缺少此保证。最安全的方法是初始化 main 中的所有关键部分(甚至在创建第一个线程之前),但这很不方便。静态本地的想法是它会在第一次需要时(按需)创建临界区实例。但如果没有线程安全保证,它是依赖于实现的。

以上是关于静态函数范围对象的构造是线程安全的吗?的主要内容,如果未能解决你的问题,请参考以下文章

lock(objlocker) 是不是使该对象在应用程序范围内线程安全?静态成员是不是自动线程安全?

有啥可以使静态布尔线程安全的吗?

单例实例声明为 GetInstance 方法的静态变量,它是线程安全的吗? [复制]

单例实例声明为 GetInstance 方法的静态变量,它是线程安全的吗? [复制]

如何安全发布对象

如果不修改静态类变量,非同步静态方法是不是线程安全?