我们是否需要互斥体来访问 C++11 - 多线程中单例对象中的数据字段?

use*_*119 4 c++ multithreading mutex c++11

据我了解,C++ 11 具有静态线程安全性。这意味着获取/创建单例的实例没有问题。这是因为静态现在是线程安全的,并且在多线程环境中不会引起任何问题。然而,问题是我是否需要一个互斥体来访问单例中的数据字段。真的和单例有关系吗?在多线程环境中访问数据字段时,即使模式不是单例,是否也需要互斥锁。这是完整的代码。

class Singleton
{
private:

Singleton()
{
   std::cout<<"Singleton Initialized"<<std::endl;
}

Singleton(Singleton const&)
{
   std::cout<<"Singleton copy constructor not allowed"<<std::endl;
}

Singleton& operator=(Singleton const&)
{
   std::cout<<"Singleton Assignment Operator not allowed"<<std::endl;
}

~Singleton()
{
   std::cout<<"Singleton Destructor not allowed"<<std::endl;
}

int m_data;

public:

int getData()
{
   return m_data;
}

void setData(int data)
{
   m_data = data;

}

static Singleton * instance( )
{
   static Singleton *single_manager = new Singleton();
   return single_manager;

}

};
Run Code Online (Sandbox Code Playgroud)

pet*_*hen 5

长话短说:博士:是的。

C++ 11“静态线程安全”可防止以下情况中的竞争条件:

static Singleton *single_manager = new Singleton();
Run Code Online (Sandbox Code Playgroud)

如果没有这个,可能的问题是:

  • Singleton() 实例化两次
  • 不同的线程看到不同的 Singleton 实例
  • 单例初始化对其他线程不可见

但是,当从不同线程调用它们时,对m_data(即GetData和)的任何访问都需要互斥体。SetData


在哪里锁定- 单例内部应该有锁,还是调用者应该锁?
(根据评论)

这个问题没有简单的答案。

我会向单例类添加一个锁,并在 GetData / SetData 中锁定。理由:这几乎总是默认用例,并减轻了调用者的负担。

但是:
内部锁并不总是足够的,例如,如果您有:

Singleton * s = Singleton::instance();
int x = s->GetData();
x = x + 1;
s->SetData(x);
Run Code Online (Sandbox Code Playgroud)

调用者需要在 Get/Modify/Set 周围使用外部锁。

反对内锁的论据如下:

  • 一般情况下,内部锁是不够的,即有些情况下调用者需要自己的锁
  • 当调用者无论如何都必须锁定时,内部锁定会成为一个可能很重要的性能问题。

结论: 如果您可以避免手头数据的 get-modify-set 场景,那么添加内部锁(例如 API 可以包含 )IncrementData方法似乎是明智的做法。清楚地记录接口的线程安全性(例如“此类不是线程安全的,需要外部锁定”或“单个函数调用是线程安全的”)


笔记:

是的:正如您已经想到的:这与该类是单例无关。
如果数据实际上只是一个 int,则可以使用“原子整数”(例如来自 boost:.atomic)。不过,获取/设置/修改问题仍然存在。