一、(LINUX 執行緒同步) 引入

gaopengtttt發表於2017-04-25
原創水平有限有誤請指出

執行緒相比程式有著先天的資料共享的優勢,如下圖,執行緒共享了程式除棧區以外的所有記憶體區域如下圖所示:


但是這種共享有時候也會帶來問題,簡單的考慮如下C++程式碼:

點選(此處)摺疊或開啟

  1. /*************************************************************************
  2.   > File Name: error.cpp
  3.   > Author: gaopeng QQ:22389860 all right reserved
  4.   > Mail: gaopp_200217@163.com
  5.   > Created Time: Mon 15 May 2017 12:01:33 AM CST
  6.  ************************************************************************/

  7. #include<iostream>
  8. #include <pthread.h>
  9. #include <string.h>
  10. #define MAXOUT 1000000
  11. using namespace std;



  12. class testc
  13. {
  14.         private:
  15.                 int a;
  16.         public:
  17.                 testc()
  18.                 {
  19.                         a = 1;
  20.                 }
  21.                 testc& operator++()
  22.                 {
  23.                         int b = 0;
  24.                         b = a;
  25.                         a = b+1;
  26.                         return *this;
  27.                 }
  28.                 void prit()
  29.                 {
  30.                         cout<<a<<endl;
  31.                 }
  32. };


  33. testc test = test;


  34. void* testp(void* arg)
  35. {
  36.         int i = MAXOUT;
  37.         while(i--)
  38.         {
  39.                 ++test;
  40.                 cout<<pthread_self() <<":";
  41.                 test.prit();
  42.         }
  43. }




  44. int main(void)
  45. {
  46.         pthread_t tid[3];
  47.         int er;
  48.         int i = 0;

  49.         while(i<3)
  50.         {

  51.                 if ((er = pthread_create(tid+i,NULL,testp,NULL) )!=0 )
  52.                 {
  53.                         strerror(er);
  54.                         return -1;
  55.                 }
  56.                 i++;
  57.         }

  58.         i = 0;

  59.         while(i<3)
  60.         {
  61.                 pthread_join(*(tid+i),NULL);
  62.                 i++;
  63.         }
  64.         cout<<"last numer: ";
  65.         test.prit();
  66. }



實際上很簡單我就是建立了一個全域性變數,這個全域性變數是全部執行緒共享,我開啟了3個執行緒同時對裡面的共享
資料進行更改,當
#define MAXOUT 100 
的時候這個時候最後輸出如下:
last numer: 300
顯然沒有問題,但是如果我將
#define MAXOUT 1000000
增大到100W的時候最後輸出為:
last numer: 2999972
我們明顯的感覺到資料不對了,少了一些
出現這種問題在於,多個執行緒對同一個共享資源進行訪問,因為執行緒是CPU排程的單位,而

點選(此處)摺疊或開啟

  1. {
  2.                         int b = 0;
  3.                         b = a;
  4.                         a = b+1;
  5.                         return *this;
  6.                 }


假設這個時候a 為 1000:
這段程式碼並不是原子性的,假設執行緒1正在修改
= a;
這個時候時間片用完,執行緒2也執行這段程式碼,因為
= b+1;並沒有執行,所以共享區域的資料還沒有變化
執行緒2拿到的資料任然是1000,也執行如下程式碼
= a;
= b+1;
這個時候執行緒2完成了修改,a為1001
當下一次執行緒1拿到CPU時間片,再次從就緒狀轉到執行態,接著
執行
= b+1;
因為執行緒1中的b還是1000,所以a再次修改為1001,實際這個時候
是1002才對,
執行緒2的修改被覆蓋,出現上面的問題,透過語言的描述
也許不太明白,下面的時序圖會幫助理解:



遇到這種問題當然就引入了我們的執行緒間同步的方法,常用的
1、互斥鎖、條件變數
2、讀寫鎖
3、訊號量
4、spinlock
他們保護的是一段臨界區程式碼,所謂臨界區就是可能出現對同一個共享資源進行修改的程式碼,比如我這裡

點選(此處)摺疊或開啟

  1. {
  2.                         int b = 0;
  3.                         b = a;
  4.                         a = b+1;
  5.                         return *this;
  6.                 }


就是臨界區程式碼
後面將對他們進行描述,這裡我們簡單實用靜態互斥鎖進行解決這個問題。

點選(此處)摺疊或開啟

  1. //原子操作 加鎖
  2.                 pthread_mutex_lock(&mtx);
  3.                 ++test;
  4.                 pthread_mutex_unlock(&mtx);
  5.                 //原子操作 解鎖
  6.                 cout<<pthread_self() <<":";
  7.                 test.prit()
實際上我們就是保護了運算子過載的testc& operator++()
臨界區的選擇應該儘量小,避免對多執行緒的併發性產生較大的效能影響


具體程式碼如下:

點選(此處)摺疊或開啟

  1. /*************************************************************************
  2.   > File Name: error.cpp
  3.   > Author: gaopeng QQ:22389860 all right reserved
  4.   > Mail: gaopp_200217@163.com
  5.   > Created Time: Mon 15 May 2017 12:01:33 AM CST
  6.  ************************************************************************/

  7. #include<iostream>
  8. #include <pthread.h>
  9. #include <string.h>
  10. #define MAXOUT 1000000
  11. using namespace std;

  12. static pthread_mutex_t mtx=PTHREAD_MUTEX_INITIALIZER;


  13. class testc
  14. {
  15.         private:
  16.                 int a;
  17.         public:
  18.                 testc()
  19.                 {
  20.                         a = 1;
  21.                 }
  22.                 testc& operator++()
  23.                 {
  24.                         int b = 0;
  25.                         b = a;
  26.                         a = b+1;
  27.                         return *this;

  28.                 }
  29.                 void prit()
  30.                 {
  31.                         cout<<a<<endl;
  32.                 }
  33. };


  34. testc test = test;


  35. void* testp(void* arg)
  36. {
  37.         int i = MAXOUT;

  38.         while(i--)
  39.         {
  40.                 //原子操作 加鎖
  41.                 pthread_mutex_lock(&mtx);
  42.                 ++test;
  43.                 pthread_mutex_unlock(&mtx);
  44.                 //原子操作 解鎖
  45.                 cout<<pthread_self() <<":";
  46.                 test.prit();
  47.         }
  48. }




  49. int main(void)
  50. {
  51.         pthread_t tid[3];
  52.         int er;
  53.         int i = 0;

  54.         while(i<3)
  55.         {

  56.                 if ((er = pthread_create(tid+i,NULL,testp,NULL) )!=0 )
  57.                 {
  58.                         strerror(er);
  59.                         return -1;
  60.                 }
  61.                 i++;
  62.         }

  63.         i = 0;

  64.         while(i<3)
  65.         {
  66.                 pthread_join(*(tid+i),NULL);
  67.                 i++;
  68.         }
  69.         cout<<"last numer: ";
  70.         test.prit();
  71. }


注意:一個簡單型別的i++也不一定是一個原子操作,所以在涉及到併發修改共享變數的時候一定要使用
執行緒同步手段。



作者微信:

               

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

相關文章