【C++多线程】共享数据的初始化保护
需要保护的初始化举例
如对单例模式的初始化。
1 #include <iostream> 2 #include <thread> 3 #include <mutex> 4 5 using namespace std; 6 7 //单例模式 8 class MySingleTon{ 9 public: 10 class Help{ 11 public: 12 ~Help() 13 { 14 if (myTon) 15 { 16 delete myTon; 17 myTon = nullptr; 18 } 19 } 20 }; 21 //使用静态函数来创建唯一实例 22 static MySingleTon* getObj() 23 { 24 if (myTon == nullptr) 25 { 26 myTon = new MySingleTon(); 27 Help hp; //使用嵌套类对象在生命周期结束调用析构函数来delete Myton 28 } 29 return myTon; 30 } 31 //测试 32 void print() 33 { 34 cout << "This is a singleton!" << endl; 35 } 36 private: 37 // 构造函数定义为private 38 MySingleTon () { } 39 40 private: 41 // 静态成员变量 42 static MySingleTon *myTon; 43 }; 44 45 //类静态成员初始化 46 MySingleTon* MySingleTon::myTon = nullptr; 47 48 int main() 49 { 50 thread t1(MySingleTon::getObj); 51 thread t2(MySingleTon::getObj); 52 return 0; 53 }
互斥量保护共享数据初始化
直接使用互斥量来保护,当多个线程以MySingleTon::getObj()为入口函数时,多个线程在等等这个互斥量只是为了确定数据是否被初始化,这显然影响效率显得不划算。
1 #include <iostream> 2 #include <thread> 3 #include <mutex> 4 5 using namespace std; 6 7 mutex my_mutex; //互斥量 8 9 //单例模式 10 class MySingleTon{ 11 public: 12 class Help{ 13 public: 14 ~Help() 15 { 16 if (myTon) 17 { 18 delete myTon; 19 myTon = nullptr; 20 } 21 } 22 }; 23 //使用静态函数来创建唯一实例 24 static MySingleTon* getObj() 25 { 26 lock_guard<mutex> my_lock(my_mutex); 27 if (myTon == nullptr) 28 { 29 myTon = new MySingleTon(); 30 Help hp; //使用嵌套类对象在生命周期结束调用析构函数来delete Myton 31 } 32 return myTon; 33 } 34 //测试 35 void print() 36 { 37 cout << "This is a singleton!" << endl; 38 } 39 private: 40 // 构造函数定义为private 41 MySingleTon () { } 42 43 private: 44 // 静态成员变量 45 static MySingleTon *myTon; 46 }; 47 48 //类静态成员初始化 49 MySingleTon* MySingleTon::myTon = nullptr; 50 51 int main() 52 { 53 thread t1(MySingleTon::getObj); 54 thread t2(MySingleTon::getObj); 55 return 0; 56 }
双重检查模式检查模式可以解决多个线程等待的问题,但是这也存在一重检查处的读数据与二重检查中的写数据不同步的条件竞争。
... static MySingleTon* getObj() { if (myTon == nullptr) //一重检查 { lock_guard<mutex> my_lock(my_mutex); if (myTon == nullptr) //二重检查 { myTon = new MySingleTon(); Help hp; //使用嵌套类对象在生命周期结束调用析构函数来delete Myton } } return myTon; } ...
std::call_once()和std::once_flag
互斥量是最通用的机制,但其并非保护共享数据的唯一方式。如果纯粹是为了保护共享数据其初始化过程会给性能带来影响。为了提高性能,C++标准提供了一种纯粹保护共享数据初始化过程的机制,即std::call_once()和std::once_flag。
std::call_once()的使用方式是std::call_once(flag, func),即通过std::once_flag的对象flag来保证函数func只被调用一次。
1 #include <iostream> 2 #include <thread> 3 #include <mutex> 4 5 using namespace std; 6 7 mutex my_mutex; //互斥量 8 once_flag flag; //调用一次的标记 9 10 //单例模式 11 class MySingleTon{ 12 public: 13 class Help{ 14 public: 15 ~Help() 16 { 17 if (myTon) 18 { 19 delete myTon; 20 myTon = nullptr; 21 } 22 } 23 }; 24 25 //初始化部分 26 static MySingleTon* init() 27 { 28 myTon = new MySingleTon(); 29 Help hp; //使用嵌套类对象在生命周期结束调用析构函数来delete Myton 30 } 31 //使用静态函数来创建唯一实例 32 static MySingleTon* getObj() 33 { 34 call_once(flag, init); //调用一次后,flag变化,其他线程不再调用init函数 35 return myTon; 36 } 37 //测试 38 void print() 39 { 40 cout << "This is a singleton!" << endl; 41 } 42 private: 43 // 构造函数定义为private 44 MySingleTon () { } 45 46 private: 47 // 静态成员变量 48 static MySingleTon *myTon; 49 }; 50 51 //类静态成员初始化 52 MySingleTon* MySingleTon::myTon = nullptr; 53 54 int main() 55 { 56 thread t1(MySingleTon::getObj); 57 thread t2(MySingleTon::getObj); 58 return 0; 59 }
菜鸟手记。