如何在不使用<mutex>的情况下在C ++ 11中实现multithreading安全单例
现在,C + + 11有multithreading,我想知道什么是正确的方式来实现惰性初始化单例没有使用互斥(出于原因)。 我想出了这个,但是tbh我不擅长编写无锁的代码,所以我正在寻找一些更好的解决scheme。
// ConsoleApplication1.cpp : Defines the entry point for the console application. // # include <atomic> # include <thread> # include <string> # include <iostream> using namespace std; class Singleton { public: Singleton() { } static bool isInitialized() { return (flag==2); } static bool initizalize(const string& name_) { if (flag==2) return false;// already initialized if (flag==1) return false;//somebody else is initializing if (flag==0) { int exp=0; int desr=1; //bool atomic_compare_exchange_strong(std::atomic<T>* obj, T* exp, T desr) bool willInitialize=std::atomic_compare_exchange_strong(&flag, &exp, desr); if (! willInitialize) { //some other thread CASed before us std::cout<<"somebody else CASed at aprox same time"<< endl; return false; } else { initialize_impl(name_); assert(flag==1); flag=2; return true; } } } static void clear() { name.clear(); flag=0; } private: static void initialize_impl(const string& name_) { name=name_; } static atomic<int> flag; static string name; }; atomic<int> Singleton::flag=0; string Singleton::name; void myThreadFunction() { Singleton s; bool initializedByMe =s.initizalize("1701"); if (initializedByMe) s.clear(); } int main() { while (true) { std::thread t1(myThreadFunction); std::thread t2(myThreadFunction); t1.join(); t2.join(); } return 0; }
请注意, clear()
只是为了testing,真正的单身不会有这个function。
C ++ 11不需要手动locking。 如果一个静态局部variables已经被初始化,那么并发执行应该等待。
§6.7 [stmt.dcl] p4
如果控制在variables初始化时同时进入声明,则并发执行应等待初始化完成。
因此,简单的有这样一个static
函数:
static Singleton& get(){ static Singleton instance; return instance; }
这将在C ++ 11中正常工作(当然,只要编译器正确地实现了这部分标准)。
当然, 真正的正确答案是不要使用一个单独的句点 。
对我来说,使用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. }
因为你没有像预期的那样使用代码,所以很难阅读你的方法……也就是说,单例的常见模式是调用instance()
来获得单个实例,然后使用它(也是,如果你真的想要单身,没有构造函数应该公开)。
无论如何,我不认为你的方法是安全的,考虑到两个线程试图获取单例,第一个获得更新标志将是唯一一个初始化,但initialize
函数将退出第二个,并且该线程可能在第一个线程完成初始化之前继续使用单例。
initialize
的语义被破坏。 如果您尝试描述 / logging函数的行为,您将会获得一些乐趣,并且最终将描述实现而不是简单的操作。 logging通常是双重检查devise/algorithm的简单方法:如果最终描述的不是什么 ,那么你应该重新进行devise。 特别是,不能保证在initialize
完成之后,对象实际上已经被初始化了(只有当返回的值是true
,有时如果是false
,但并不总是)。
恕我直言,最好的方式来实现单身人士是与“双检,单锁”模式,你可以在C + + 11中实现可移植: 双检查locking固定在C + + 11这种模式是快速的,创build的情况下,只需要一个指针比较,并在第一个用例安全。
如前所述,C ++ 11保证静态局部variables的构造顺序安全性本地静态variables初始化在C ++ 11中是线程安全的吗? 所以你使用这种模式是安全的。 但是,Visual Studio 2013还不支持:-( 请参阅本页面的“magic statics”一行 ,所以如果您使用的是VS2013,您仍然需要自己动手。
不幸的是,没有什么是简单的。 上面的模式引用的示例代码不能从CRT初始化调用,因为静态std :: mutex有一个构造函数,因此不能保证在第一次调用单身之前初始化,如果这个调用是side- CRT初始化的效果。 为了解决这个问题 ,你必须使用,而不是一个互斥体,而是一个指向互斥体的指针,这个指针保证在CRT初始化开始之前被零初始化。 那么你将不得不使用std :: atomic :: compare_exchange_strong来创build和使用互斥锁。
我假定C ++ 11线程安全的本地静态初始化语义即使在CRT初始化过程中被调用。
因此,如果您有C ++ 11线程安全的本地静态初始化语义,请使用它们。 如果没有,你还有一些工作要做,如果你希望你的单例在CRT初始化时是线程安全的。