如果我必须用 C++ 编写一个单例类,我将使用一个静态变量、私有构造函数和一个返回类对象的公共静态函数。但是在多线程环境中,代码会出现问题。为了避免多个线程同时访问同一个变量,Boost线程是用于同步的最佳机制吗?我的意思是设置/取消设置整个资源的锁/互斥锁。C++ 标准库中是否还有其他内置的东西,我不必下载 boost、构建东西等?我听说过 C++ Ox,但不太了解。
2 回答
C++98/03 根本没有支持线程的东西。如果您使用的是 C++98 或 03 编译器,那么您几乎会被困在使用 Boost 或某些(或多或少)特定于操作系统的东西上,例如 pthread 或 Win32 的线程原语。
C++11 有一个相当完整的线程支持库,包括互斥体、锁、线程本地存储等。
然而,我觉得有必要指出,备份并多考虑一下你是否需要/想要一个 Singleton 可能会更好。说得好听一点,单例模式在很大程度上已经失宠了。
编辑:重读这篇文章,我有点跳过了我想说的一件事:至少当我使用它们时,任何/所有单例在任何辅助线程启动之前都已完全初始化。这使得在初始化时对线程安全的担忧完全没有意义。我想在启动辅助线程之前可能有一个您无法初始化的单例,因此您需要处理这个问题,但至少立即让我觉得这是一个相当不寻常的例外,我只会在以下情况下处理/如果绝对必要的话。
对我来说,使用 c++11 实现单例的最佳方法是:
class Singleton
{
public:
static Singleton & Instance()
{
// Since it's a static variable, if the class has already been created,
// It won't be created again.
// And it **is** thread-safe in C++11.
static Singleton myInstance;
// Return a reference to our instance.
return myInstance;
}
// delete copy and move constructors and assign operators
Singleton(Singleton const&) = delete; // Copy construct
Singleton(Singleton&&) = delete; // Move construct
Singleton& operator=(Singleton const&) = delete; // Copy assign
Singleton& operator=(Singleton &&) = delete; // Move assign
// Any other public methods
protected:
Singleton()
{
// Constructor code goes here.
}
~Singleton()
{
// Destructor code goes here.
}
// And any other protected methods.
}
这是一个 c++11 功能,但通过这种方式,您可以创建一个线程安全的单例。按照新标准,这个问题就不用管了。对象初始化将仅由一个线程进行,其他线程将等待它完成。或者你可以使用 std::call_once。
如果你想独占访问单例的资源,你必须在这些函数上使用锁。
不同类型的锁:
使用atomic_flg_lck:
class SLock
{
public:
void lock()
{
while (lck.test_and_set(std::memory_order_acquire));
}
void unlock()
{
lck.clear(std::memory_order_release);
}
SLock(){
//lck = ATOMIC_FLAG_INIT;
lck.clear();
}
private:
std::atomic_flag lck;// = ATOMIC_FLAG_INIT;
};
使用原子:
class SLock
{
public:
void lock()
{
while (lck.exchange(true));
}
void unlock()
{
lck = true;
}
SLock(){
//lck = ATOMIC_FLAG_INIT;
lck = false;
}
private:
std::atomic<bool> lck;
};
使用互斥锁:
class SLock
{
public:
void lock()
{
lck.lock();
}
void unlock()
{
lck.unlock();
}
private:
std::mutex lck;
};
仅适用于Windows:
class SLock
{
public:
void lock()
{
EnterCriticalSection(&g_crit_sec);
}
void unlock()
{
LeaveCriticalSection(&g_crit_sec);
}
SLock(){
InitializeCriticalSectionAndSpinCount(&g_crit_sec, 0x80000400);
}
private:
CRITICAL_SECTION g_crit_sec;
};
atomic和atomic_flg_lck将线程保持在旋转计数中。互斥锁只是让线程休眠。如果等待时间太长,最好让线程休眠。最后一个“ CRITICAL_SECTION ”将线程保持在旋转计数中,直到消耗了一段时间,然后线程进入睡眠状态。
如何使用这些关键部分?
unique_ptr<SLock> raiilock(new SLock());
class Smartlock{
public:
Smartlock(){ raiilock->lock(); }
~Smartlock(){ raiilock->unlock(); }
};
使用 raii 成语。锁定临界区的构造函数和解锁它的析构函数。
例子
class Singleton {
void syncronithedFunction(){
Smartlock lock;
//.....
}
}
此实现是线程安全和异常安全的,因为变量锁保存在堆栈中,因此当函数范围结束(函数结束或异常)时,将调用析构函数。
我希望你觉得这很有帮助。
谢谢!!