自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

一個多線程的簡單例子讓你看清線程調(diào)度的隨機性

開發(fā) 前端
多線程并發(fā)執(zhí)行時有很多同學(xué)捋不清楚調(diào)度的隨機性會導(dǎo)致哪些問題,要知道如果訪問臨界資源不加鎖會導(dǎo)致一些突發(fā)情況發(fā)生甚至死鎖。

 [[348484]]

線程調(diào)度的幾個基本知識點

多線程并發(fā)執(zhí)行時有很多同學(xué)捋不清楚調(diào)度的隨機性會導(dǎo)致哪些問題,要知道如果訪問臨界資源不加鎖會導(dǎo)致一些突發(fā)情況發(fā)生甚至死鎖。

關(guān)于線程調(diào)度,需要深刻了解以下幾個基礎(chǔ)知識點:

調(diào)度的最小單位是輕量級進程【比如我們編寫的hello world最簡單的C程序,執(zhí)行時就是一個輕量級進程】或者線程;

每個線程都會分配一個時間片,時間片到了就會執(zhí)行下一個線程;

線程的調(diào)度有一定的隨機性,無法確定什么時候會調(diào)度;

在同一個進程內(nèi),創(chuàng)建的所有線程除了線程內(nèi)部創(chuàng)建的局部資源,進程創(chuàng)建的其他資源所有線程共享;比如:主線程和子線程都可以訪問全局變量,打開的文件描述符等。

實例

再多的理論不如一個形象的例子來的直接。

預(yù)期代碼時序

假定我們要實現(xiàn)一個多線程的實例,預(yù)期程序執(zhí)行時序如下:

 

 

期待時序

 

 

期待的功能時序:

  1. 主進程創(chuàng)建子線程,子線程函數(shù)function();
  2. 主線程count自加,并分別賦值給value1,value2;
  3. 時間片到了后切換到子線程,子線程判斷value1、value2值是否相同,如果不同就打印信息value1,value2,count的值,但是因為主線程將count先后賦值給了value1,value2,所以value1,value2的值應(yīng)該永遠(yuǎn)不相同,所以不應(yīng)該打印任何內(nèi)容;
  4. 重復(fù)2、3步驟。

代碼1

好了,現(xiàn)在我們按照這個時序編寫代碼如下:

  1. 1 #include <stdio.h> 
  2.   2 #include <stdlib.h> 
  3.   3 #include <string.h> 
  4.   4 #include <pthread.h> 
  5.   5 #include <unistd.h> 
  6.   6  
  7.   7 unsigned int value1,value2, count=0; 
  8.   8 void *function(void *arg); 
  9.   9 int main(int argc,  char *argv[]) 
  10.  10 { 
  11.  11     pthread_t  a_thread; 
  12.  12  
  13.  13     if (pthread_create(&a_thread, NULLfunctionNULL) < 0) 
  14.  14     { 
  15.  15         perror("fail to pthread_create"); 
  16.  16         exit(-1); 
  17.  17     } 
  18.  18     while ( 1 ) 
  19.  19     { 
  20.  20         count++; 
  21.  21         value1 = count
  22.  22         value2 = count
  23.  23     } 
  24.  24     return 0; 
  25.  25 } 
  26.  26  
  27.  27 void  *function(void *arg) 
  28.  28 { 
  29.  29     while ( 1 ) 
  30.  30     { 
  31.  31         if (value1 != value2) 
  32.  32         {                                                                                                                                                                                          
  33.  33             printf("count=%d , value1=%d, value2=%d\n",  count, value1, value2); 
  34.  34             usleep(100000); 
  35.  35         }      
  36.  36     } 
  37.  37     return  NULL
  38.  38 }   

乍一看,該程序應(yīng)該可以滿足我們的需要,并且程序運行的時候不應(yīng)該打印任何內(nèi)容,但是實際運行結(jié)果出乎我們意料。

編譯運行:

  1. gcc test.c -o run -lpthread 
  2. ./run 

代碼1執(zhí)行結(jié)果

執(zhí)行結(jié)果:

可以看到子程序會隨機打印一些信息,為什么還有這個執(zhí)行結(jié)果呢?其實原因很簡單,就是我們文章開頭所說的,線程調(diào)度具有䘺隨機性,我們無法規(guī)定讓內(nèi)核何時調(diào)度某個線程。有打印信息,那么這說明此時value1和value2的值是不同的,那也說明了調(diào)度子線程的時候,是在主線程向value1和value2之間的位置調(diào)度的。

 

代碼1執(zhí)行的實際時序

實際上代碼的執(zhí)行時序如下所示:

 

如上圖,在某一時刻,當(dāng)程序走到**value2 = count;**這個位置的時候,內(nèi)核對線程進行了調(diào)度,于是子進程在判斷value1和value2的值的時候,發(fā)現(xiàn)這兩個變量值不相同,就有了打印信息。

該程序在下面這兩行代碼之間調(diào)度的幾率還是很大的。

  1. value1 = count;  
  2. value2 = count

解決方法

如何來解決并發(fā)導(dǎo)致的程序沒有按預(yù)期執(zhí)行的問題呢?對于線程來說,常用的方法有posix信號量、互斥鎖,條件變量等,下面我們以互斥鎖為例,講解如何避免代碼1的問題的出現(xiàn)。

互斥鎖的定義和初始化:

  1. pthread_mutex_t  mutex; 
  2. pthread_mutex_init(&mutex, NULL

申請釋放鎖:

  1. pthread_mutex_lock(&mutex); 
  2. pthread_mutex_unlock(&mutex); 

原理:進入臨界區(qū)之前先申請鎖,如果能獲得鎖就繼續(xù)往下執(zhí)行, 如果申請不到,就休眠,直到其他線程釋放該鎖為止。

代碼2

  1. 1 #include <stdio.h> 
  2.  2 #include <stdlib.h> 
  3.  3 #include <string.h> 
  4.  4 #include <pthread.h> 
  5.  5 #include <unistd.h> 
  6.  6 #define _LOCK_ 
  7.  7 unsigned int value1,value2, count=0; 
  8.  8 pthread_mutex_t  mutex; 
  9.  9 void *function(void *arg); 
  10. 10  
  11. 11 int main(int argc,  char *argv[]) 
  12. 12 { 
  13. 13     pthread_t  a_thread; 
  14. 14           
  15. 15     if (pthread_mutex_init(&mutex, NULL) < 0)                                                                                                                                                           
  16. 16     { 
  17. 17         perror("fail to mutex_init"); 
  18. 18         exit(-1); 
  19. 19     } 
  20. 20  
  21. 21     if (pthread_create(&a_thread, NULLfunctionNULL) < 0) 
  22. 22     { 
  23. 23         perror("fail to pthread_create"); 
  24. 24         exit(-1); 
  25. 25     } 
  26. 26     while ( 1 ) 
  27. 27     { 
  28. 28         count++; 
  29. 29 #ifdef  _LOCK_ 
  30. 30         pthread_mutex_lock(&mutex); 
  31. 31 #endif 
  32. 32         value1 = count
  33. 33         value2 = count
  34. 34 #ifdef  _LOCK_ 
  35. 35         pthread_mutex_unlock(&mutex); 
  36. 36 #endif 
  37. 37     } 
  38. 38     return 0; 
  39. 39  } 
  40. 0  
  41. 41 void  *function(void *arg) 
  42. 42 { 
  43. 43      while ( 1 ) 
  44. 44      { 
  45. 45 #ifdef _LOCK_ 
  46. 46         pthread_mutex_lock(&mutex); 
  47. 47 #endif            
  48. 48  
  49. 49         if (value1 != value2)   
  50. 50         { 
  51. 51             printf("count=%d , value1=%d, value2=%d\n",  count, value1, value2); 
  52. 52             usleep(100000); 
  53. 53         }      
  54. 54 #ifdef _LOCK_ 
  55. 55         pthread_mutex_unlock(&mutex); 
  56. 56 #endif 
  57. 57      } 
  58. 58      return  NULL
  59. 59  }      

如上述代碼所示:主線程和子線程要訪問臨界資源value1,value2時,都必須先申請鎖,獲得鎖之后才可以訪問臨界資源,訪問完畢再釋放互斥鎖。該代碼執(zhí)行之后就不會打印任何信息。我們來看下,如果程序在下述代碼之間產(chǎn)生調(diào)度時,程序的時序圖。

  1. value1 = count;  
  2. value2 = count

時序圖如下:

 


如上圖所示:

 

 

  1. 時刻n,主線程獲得mutex,從而進入臨界區(qū);
  2. 時刻n+1,時間片到了,切換到子線程;
  3. n+2時刻子線程申請不到鎖mutex,所以放棄cpu,進入休眠;
  4. n+3時刻,主線程釋放mutex,離開臨界區(qū),并喚醒阻塞在mutex的子線程,子線程申請到mutex,進入臨界區(qū);
  5. n+4時刻,子線程離開臨界區(qū),釋放mutex。

可以看到,加鎖之后,即使主線程在value2 =count; 之前產(chǎn)生了調(diào)度,子線程由于獲取不到mutex,會進入休眠,只有主線程出了臨界區(qū),子線程才能獲得mutex,訪問value1和value2,就永遠(yuǎn)不會打印信息,就實現(xiàn)了我們預(yù)期的代碼時序。

總結(jié)

實際項目中,可能程序的并發(fā)的情況可能會更加復(fù)雜,比如多個cpu上運行的任務(wù)之間,cpu運行的任務(wù)和中斷之間,中斷和中斷之間,都有可能并發(fā)。

有些調(diào)度的概率雖然很小,但是不代表不發(fā)生,而且由于資源同步互斥導(dǎo)致的問題,很難復(fù)現(xiàn),縱觀Linux內(nèi)核代碼,所有的臨界資源都會對應(yīng)鎖。

多閱讀Linux內(nèi)核源碼,學(xué)向大神學(xué)習(xí),與大神神交。

正所謂代碼讀百遍,其義自見!熟讀代碼千萬行,不會編寫也會抄!

關(guān)于內(nèi)核和應(yīng)用程序的同步互斥的知識點,可以查看一口君的其他文章。

本文轉(zhuǎn)載自微信公眾號「一口Linux」,可以通過以下二維碼關(guān)注。轉(zhuǎn)載本文請聯(lián)系一口Linux公眾號。

 

責(zé)任編輯:武曉燕 來源: 一口Linux
相關(guān)推薦

2013-04-25 09:55:21

進程線程

2016-01-21 09:55:51

2024-12-03 00:44:50

2019-09-18 15:09:50

進程線程操作系統(tǒng)

2021-12-27 09:31:20

HashtableJava隨機數(shù)

2009-07-14 16:02:42

JDBC例子

2023-10-26 21:44:02

Java多線程方法

2013-06-08 13:07:23

Java線程池調(diào)度器

2022-09-08 06:23:37

C++HTTP 服務(wù)器

2010-04-19 17:21:36

Oracle寫文件

2020-03-26 17:00:53

HashMapputJava

2022-10-31 07:09:15

拷貝代碼項目

2020-04-03 09:45:09

Java多線程接口

2023-12-13 10:46:27

2024-10-14 16:25:59

C#線程鎖代碼

2017-12-07 15:05:50

全球互聯(lián)網(wǎng)創(chuàng)新峰會

2010-03-24 10:32:05

Python多線程

2024-11-14 00:08:14

C#技術(shù)

2009-08-31 14:45:15

C#.NET多線程應(yīng)用

2024-11-15 11:00:00

C#多線程
點贊
收藏

51CTO技術(shù)棧公眾號