C++單例懶漢式和多執行緒問題(MUTEX 保護)

gaopengtttt發表於2017-04-10
單例懶漢式和多執行緒問題


作為單例模式,是在整個程式執行期間只會建立一份記憶體空間,為了達到這個目標
1、需要將建構函式設定為私有成員
2、需要一個私有的靜態指標指向自身
3、需要一個公有的靜態函式將這個上面的靜態指標露出來

如下的程式碼就是一個懶漢式的單例

點選(此處)摺疊或開啟

  1. #include<iostream>
  2. using namespace std;

  3. class single_ins
  4. {
  5. private:
  6.     int a;
  7.     int b;
  8.     single_ins()
  9.     {
  10.         a= 0;
  11.         b= 0;
  12.     }
  13.     static single_ins* myc;
  14. public:
  15.     void setval(const int& a,const int& b)
  16.     {
  17.         this->a = a;
  18.         this->b = b;
  19.     }

  20.     void print()
  21.     {
  22.       cout<<"a:"<<a<<endl;
  23.       cout<<"b:"<<b<<endl;
  24.     }


  25.     static single_ins* setp()
  26.     {
  27.         //?
  28.         if(myc == NULL)
  29.         {
  30.             myc = new single_ins;
  31.         }
  32.         //?
  33.         return myc;
  34.     }

  35.     static void pfree()
  36.     {
  37.         if(myc != NULL)
  38.         {
  39.             delete myc;
  40.             myc = NULL;
  41.         }
  42.     }

  43. };

  44. //? init static value
  45. single_ins* single_ins::myc = NULL;
  46. //nit static value
  47. //single_ins* single_ins::myc = new single_ins;

  48. int main()
  49. {
  50.     single_ins* a = single_ins::setp();
  51.     single_ins* b = single_ins::setp();
  52.     a->setval(10,20);
  53.     b->print();

  54.     cout<<a<<" "<<b<<endl;
  55.     single_ins::pfree();

  56. }
但是上面的程式碼有明顯的問題,就是遇到多執行緒的情況下,因為多個執行緒如果同事建立記憶體,由於彼此之間
並不能及時檢查到記憶體已經分配,會分配多個記憶體,這個時候我們至少需要一個執行緒間同步手段來讓他們之間
序列的執行,這個時候就涉及到兩次檢查(double check)
如果沒有mutex保護就是下面這個程式:

點選(此處)摺疊或開啟

  1. #include <iostream>
  2. #include <unistd.h>
  3. using namespace std;


  4. //單列模式
  5. class single_ins
  6. {
  7.         private:
  8.                 int a;
  9.                 int b;
  10.                 single_ins()
  11.                 {
  12.                         cout<<"con begin\n";
  13.                         a= 0;
  14.                         b= 0;
  15.                         sleep(10); //故意拖長建構函式執行時間,造成懶漢式多執行緒問題
  16.                         cout<<"con end\n";
  17.                 }
  18.                 static single_ins* myc;//單例需要一個靜態指標
  19.                 static int cnt;//構造呼叫次數統計
  20.         public:
  21.                 void setval(const int& a,const int& b)
  22.                 {
  23.                         this->a = a;
  24.                         this->b = b;
  25.                 }

  26.                 void print()
  27.                 {
  28.                         cout<<"a:"<<a<<endl;
  29.                         cout<<"b:"<<b<<endl;
  30.                         cout<<cnt<<endl;
  31.                 }


  32.                 static single_ins* setp() //函式獲得指標值賦給靜態成員指標變數
  33.                 {
  34.                         //懶漢式
  35.                         if(myc == NULL)
  36.                         {
  37.                                 myc = new single_ins;
  38.                                 cnt++;
  39.                         }
  40.                         //懶漢式
  41.                         return myc;
  42.                 }
  43.                 static void pfree()
  44.                 {
  45.                         if(myc != NULL)
  46.                         {
  47.                                 delete myc;
  48.                                 myc = NULL;
  49.                         }
  50.                 }
  51. };

  52. //懶漢式 init static value
  53. single_ins* single_ins::myc = NULL;
  54. int single_ins::cnt = 0;
  55. //餓漢試 init static value
  56. //single_ins* single_ins::myc = new single_ins;
  57. /*
  58.    懶漢式的問題在於多執行緒呼叫的時候會出現問題,很可能同時建立出多個記憶體空間,
  59.    而不是單列了。
  60.    */
  61. void* main21(void* argc)
  62. {
  63.         single_ins* inp = (single_ins*)argc;

  64.         inp = single_ins::setp();
  65.         inp->setval(10,20);
  66.         inp->print();
  67.         cout<<inp<<"\n";
  68.         return NULL;
  69. }


  70. int main(void)
  71. {
  72.         pthread_t tid;
  73.         single_ins* a[3] = {NULL};
  74.         void* tret[3] = {NULL};
  75.         for(int i = 0 ; i<3; i++)
  76.         {
  77.                 pthread_create(&tid,NULL,main21,(void*)a[i]);
  78.                 //pthread_join(tid, &(tret[i]));
  79.         }
  80.         sleep(50);
  81.         single_ins::pfree();

  82. }
會跑出結果
con begin
con begin
con begin
con end
a:10
b:20
1
0x7fc3880008c0
con end
a:10
b:20
2
0x7fc3800008c0
con end
a:10
b:20
3
0x7fc3840008c0


可以看到
0x7fc3880008c0 0x7fc3800008c0 0x7fc3840008c0
明顯是3個不同的記憶體空間 這就不對了,而且可以看到建構函式
呼叫了3次
為此我們使用mutex來保護臨時

如下:
 static single_ins* setp() //函式獲得指標值賦給靜態成員指標變數
 39         {
 40             //懶漢式
 41             if(myc == NULL)
 42             {
 43                 pthread_mutex_lock(&counter_mutex); //mutex 保護臨界區
 44                 if(myc == NULL) //兩次檢查
 45                 {
 46                     myc = new single_ins;                                                                                                                           
 47                     cnt++;
 48                 }
 49                 pthread_mutex_unlock(&counter_mutex); //mutex結束
 50             }


這樣程式碼如下:

點選(此處)摺疊或開啟

  1. #include <iostream>
  2. #include <unistd.h>
  3. using namespace std;

  4. pthread_mutex_t counter_mutex = PTHREAD_MUTEX_INITIALIZER;

  5. //單列模式
  6. class single_ins
  7. {
  8.         private:
  9.                 int a;
  10.                 int b;
  11.                 single_ins()
  12.                 {
  13.                         cout<<"con begin\n";
  14.                         a= 0;
  15.                         b= 0;
  16.                         sleep(10); //故意拖長建構函式執行時間,造成懶漢式多執行緒問題
  17.                         cout<<"con end\n";
  18.                 }
  19.                 static single_ins* myc;//單例需要一個靜態指標
  20.                 static int cnt;//構造呼叫次數統計
  21.         public:
  22.                 void setval(const int& a,const int& b)
  23.                 {
  24.                         this->a = a;
  25.                         this->b = b;
  26.                 }

  27.                 void print()
  28.                 {
  29.                         cout<<"a:"<<a<<endl;
  30.                         cout<<"b:"<<b<<endl;
  31.                         cout<<cnt<<endl;
  32.                 }


  33.                 static single_ins* setp() //函式獲得指標值賦給靜態成員指標變數
  34.                 {
  35.                         //懶漢式
  36.                         if(myc == NULL)
  37.                         {
  38.                                 pthread_mutex_lock(&counter_mutex); //mutex 保護臨界區
  39.                                 if(myc == NULL) //兩次檢查
  40.                                 {
  41.                                         myc = new single_ins;
  42.                                         cnt++;
  43.                                 }
  44.                                 pthread_mutex_unlock(&counter_mutex); //mutex結束
  45.                         }
  46.                         //懶漢式
  47.                         return myc;
  48.                 }
  49.                 static void pfree()
  50.                 {
  51.                         if(myc != NULL)
  52.                         {
  53.                                 delete myc;
  54.                                 myc = NULL;
  55.                         }
  56.                 }
  57. };

  58. //懶漢式 init static value
  59. single_ins* single_ins::myc = NULL;
  60. int single_ins::cnt = 0;
  61. //餓漢試 init static value
  62. //single_ins* single_ins::myc = new single_ins;
  63. /*
  64.    懶漢式的問題在於多執行緒呼叫的時候會出現問題,很可能同時建立出多個記憶體空間,
  65.    而不是單列了。
  66.    */



  67. void* main21(void* argc)
  68. {
  69.         single_ins* inp = (single_ins*)argc;

  70.         inp = single_ins::setp();
  71.         inp->setval(10,20);
  72.         inp->print();
  73.         cout<<inp<<"\n";
  74.         return NULL;
  75. }


  76. int main(void)
  77. {
  78.         pthread_t tid;
  79.         single_ins* a[3] = {NULL};
  80.         void* tret[3] = {NULL};
  81.         for(int i = 0 ; i<3; i++)
  82.         {
  83.                 pthread_create(&tid,NULL,main21,(void*)a[i]);
  84.                 //pthread_join(tid, &(tret[i]));
  85.         }
  86.         sleep(50);
  87.         single_ins::pfree();

  88. }
跑出的結果如下:

con begin
con end
a:10a:10
b:20
1
0x7f21f40008c0


b:20
1
0x7f21f40008c0
a:10
b:20
1
0x7f21f40008c0


現在就是正常的了。所以懶漢試單例遇到多執行緒一定要注意,餓漢試沒有問題。
當然這是一個小列子而已,執行緒安全是一個很大的話題,特別需要注意。

作者微信:

               

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/7728585/viewspace-2136964/,如需轉載,請註明出處,否則將追究法律責任。

相關文章