关于设计模式:Singleton Synchronization C ++

Singleton Synchronization C++

如果我必须在C ++中编写单例类,我将使用静态变量,私有构造函数& 一个返回类对象的公共静态函数。 但是在多线程环境中,代码会出现问题。 为了避免多个线程同时访问同一个变量,Boost线程是用于同步的最佳机制吗? 我的意思是在资源上设置/取消设置锁/互斥锁。 在C ++标准库中是否有其他内置的内容,我不必下载boost,构建东西等? 我听说过C ++ Ox但不太了解。


C ++ 98/03完全不支持线程。如果您正在使用C ++ 98或03编译器,那么您几乎不得不使用Boost,或某些(或多或少)特定于操作系统的东西,例如pthreads或Win32的线程原语。

C ++ 11有一个相当完整的线程支持库,包括互斥锁,锁,线程本地存储等。

然而,我觉得有必要指出,备份并做更多考虑是否需要/想要一个Singleton可能会更好。说实话,单身模式在很大程度上已经失宠了。

编辑:重读这个,我有点想跳过一件事:至少当我使用它们时,任何/所有单例都在任何辅助线程启动之前完全初始化。这引起了对初始化中线程安全性的关注,这完全没有实际意义。我想在你启动辅助线程之前你可能有一个你无法初始化的单例,所以你需要处理这个问题,但至少对它来说这是一个非常不寻常的例外,我只会在/如果绝对必要。


对我来说,使用c ++ 11实现单例的最佳方法是:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
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功能,但通过这种方式,您可以创建一个线程安全的Singleton。根据新标准,不再需要关心这个问题。对象初始化只能由一个线程完成,其他线程将等待它完成。或者你可以使用std :: call_once。

如果要对单例资源进行独占访问,则必须对这些函数使用锁定。

不同类型的锁:

使用atomic_flg_lck:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
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;
};

使用原子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
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;
};

使用互斥锁:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class SLock
{
public:
  void lock()
  {
    lck.lock();
  }

  void unlock()
  {
    lck.unlock();
  }

private:
  std::mutex lck;
};

仅适用于Windows:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
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_flg_lck使线程保持旋转计数。 Mutex只是睡觉了。如果等待时间太长也许最好睡眠线程。最后一个"CRITICAL_SECTION"使线程保持旋转计数直到消耗时间,然后线程进入休眠状态。

如何使用这些关键部分?

1
2
3
4
5
6
7
unique_ptr<SLock> raiilock(new SLock());

class Smartlock{
public:
  Smartlock(){ raiilock->lock(); }
  ~Smartlock(){ raiilock->unlock(); }
};

使用raii成语。用于锁定关键部分的构造函数和用于解锁它的析构函数。

1
2
3
4
5
6
7
8
class Singleton {

   void syncronithedFunction(){
      Smartlock lock;
      //.....
   }

}

此实现是线程安全且异常安全的,因为变量锁保存在堆栈中,因此当函数作用域结束(函数结束或异常)时,将调用析构函数。

我希望你觉得这很有帮助。

谢谢!!