Linux多線程——使用信號量同步線程

http://blog.csdn.net/ljianhui/article/details/10813469/

信號量、同步這些名詞在進程間通信時就已經說過,在這里它們的意思是相同的,只不過是同步的對象不同而已。但是下面介紹的信號量的接口是用于線程的信號量,注意不要跟用于進程間通信的信號量混淆,關于用于進程間通信的信號量的詳細介紹可以參閱我的另一篇博文:Linux進程間通信——使用信號量。相似地,線程同步是控制線程執行和訪問臨界區域的方法

一、什么是信號量
線程的信號量與進程間通信中使用的信號量的概念是一樣,它是一種特殊的變量,它可以被增加或減少,但對其的關鍵訪問被保證是原子操作。如果一個程序中有多個線程試圖改變一個信號量的值,系統將保證所有的操作都將依次進行。

而只有0和1兩種取值的信號量叫做二進制信號量,在這里將重點介紹。而信號量一般常用于保護一段代碼,使其每次只被一個執行線程運行。我們可以使用二進制信號量來完成這個工作。

二、信號量的接口和使用

信號量的函數都以sem_開頭,線程中使用的基本信號量函數有4個,它們都聲明在頭文件semaphore.h中。

1、sem_init函數
該函數用于創建信號量,其原型如下:
[cpp]?view plain?copy
  1. int?sem_init(sem_t?*sem,?int?pshared,?unsigned?int?value);??
該函數初始化由sem指向的信號對象,設置它的共享選項,并給它一個初始的整數值。pshared控制信號量的類型,如果其值為0,就表示這個信號量是當前進程的局部信號量,否則信號量就可以在多個進程之間共享,value為sem的初始值。調用成功時返回0,失敗返回-1.

2、sem_wait函數
該函數用于以原子操作的方式將信號量的值減1。原子操作就是,如果兩個線程企圖同時給一個信號量加1或減1,它們之間不會互相干擾。它的原型如下:
[cpp]?view plain?copy
  1. int?sem_wait(sem_t?*sem);??
sem指向的對象是由sem_init調用初始化的信號量。調用成功時返回0,失敗返回-1.

3、sem_post函數
該函數用于以原子操作的方式將信號量的值加1。它的原型如下:
[cpp]?view plain?copy
  1. int?sem_post(sem_t?*sem);??
與sem_wait一樣,sem指向的對象是由sem_init調用初始化的信號量。調用成功時返回0,失敗返回-1.

4、sem_destroy函數
該函數用于對用完的信號量的清理。它的原型如下:
[cpp]?view plain?copy
  1. int?sem_destroy(sem_t?*sem);??
成功時返回0,失敗時返回-1.

三、使用信號量同步線程

下面以一個簡單的多線程程序來說明如何使用信號量進行線程同步。在主線程中,我們創建子線程,并把數組msg作為參數傳遞給子線程,然后主線程等待直到有文本輸入,然后調用sem_post來增加信號量的值,這樣就會立刻使子線程從sem_wait的等待中返回并開始執行。線程函數在把字符串的小寫字母變成大寫并統計輸入的字符數量之后,它再次調用sem_wait并再次被阻塞,直到主線程再次調用sem_post增加信號量的值。

[cpp]?view plain?copy
  1. #include?<unistd.h>??
  2. #include?<pthread.h>??
  3. #include?<semaphore.h>??
  4. #include?<stdlib.h>??
  5. #include?<stdio.h>??
  6. #include?<string.h>??
  7. ??
  8. //線程函數??
  9. void?*thread_func(void?*msg);??
  10. sem_t?sem;//信號量??
  11. ??
  12. #define?MSG_SIZE?512??
  13. ??
  14. int?main()??
  15. {??
  16. ????int?res?=?-1;??
  17. ????pthread_t?thread;??
  18. ????void?*thread_result?=?NULL;??
  19. ????char?msg[MSG_SIZE];??
  20. ????//初始化信號量,其初值為0??
  21. ????res?=?sem_init(&sem,?0,?0);??
  22. ????if(res?==?-1)??
  23. ????{??
  24. ????????perror("semaphore?intitialization?failed\n");??
  25. ????????exit(EXIT_FAILURE);??
  26. ????}??
  27. ????//創建線程,并把msg作為線程函數的參數??
  28. ????res?=?pthread_create(&thread,?NULL,?thread_func,?msg);??
  29. ????if(res?!=?0)??
  30. ????{??
  31. ????????perror("pthread_create?failed\n");??
  32. ????????exit(EXIT_FAILURE);??
  33. ????}??
  34. ????//輸入信息,以輸入end結束,由于fgets會把回車(\n)也讀入,所以判斷時就變成了“end\n”??
  35. ????printf("Input?some?text.?Enter?'end'to?finish...\n");??
  36. ????while(strcmp("end\n",?msg)?!=?0)??
  37. ????{??
  38. ????????fgets(msg,?MSG_SIZE,?stdin);??
  39. ????????//把信號量加1??
  40. ????????sem_post(&sem);??
  41. ????}??
  42. ??
  43. ????printf("Waiting?for?thread?to?finish...\n");??
  44. ????//等待子線程結束??
  45. ????res?=?pthread_join(thread,?&thread_result);??
  46. ????if(res?!=?0)??
  47. ????{??
  48. ????????perror("pthread_join?failed\n");??
  49. ????????exit(EXIT_FAILURE);??
  50. ????}??
  51. ????printf("Thread?joined\n");??
  52. ????//清理信號量??
  53. ????sem_destroy(&sem);??
  54. ????exit(EXIT_SUCCESS);??
  55. }??
  56. ??
  57. void*?thread_func(void?*msg)??
  58. {??
  59. ????//把信號量減1??
  60. ????sem_wait(&sem);??
  61. ????char?*ptr?=?msg;??
  62. ????while(strcmp("end\n",?msg)?!=?0)??
  63. ????{??
  64. ????????int?i?=?0;??
  65. ????????//把小寫字母變成大寫??
  66. ????????for(;?ptr[i]?!=?'\0';?++i)??
  67. ????????{??
  68. ????????????if(ptr[i]?>=?'a'?&&?ptr[i]?<=?'z')??
  69. ????????????{??
  70. ????????????????ptr[i]?-=?'a'?-?'A';??
  71. ????????????}??
  72. ????????}??
  73. ????????printf("You?input?%d?characters\n",?i-1);??
  74. ????????printf("To?Uppercase:?%s\n",?ptr);??
  75. ????????//把信號量減1??
  76. ????????sem_wait(&sem);??
  77. ????}??
  78. ????//退出線程??
  79. ????pthread_exit(NULL);??
  80. }??
運行結果如下:



從運行的結果來看,這個程序的確是同時在運行兩個線程,一個控制輸入,另一個控制處理統計和輸出。

四、分析此信號量同步程序的缺陷
但是這個程序有一點點的小問題,就是這個程序依賴接收文本輸入的時間足夠長,這樣子線程才有足夠的時間在主線程還未準備好給它更多的單詞去處理和統計之前處理和統計出工作區中字符的個數。所以當我們連續快速地給它兩組不同的單詞去統計時,子線程就沒有足夠的時間支執行,但是信號量已被增加不止一次,所以字符統計線程(子線程)就會反復處理和統計字符數目,并減少信號量的值,直到它再次變成0為止。

為了更加清楚地說明上面所說的情況,修改主線程的while循環中的代碼,如下:
[cpp]?view plain?copy
  1. printf("Input?some?text.?Enter?'end'to?finish...\n");??
  2. while(strcmp("end\n",?msg)?!=?0)??
  3. {??
  4. ????if(strncmp("TEST",?msg,?4)?==?0)??
  5. ????{??
  6. ????????strcpy(msg,?"copy_data\n");??
  7. ????????sem_post(&sem);??
  8. ????}??
  9. ????fgets(msg,?MSG_SIZE,?stdin);??
  10. ????//把信號量加1??
  11. ????sem_post(&sem);??
  12. }??
重新編譯程序,此時運行結果如下:



當我們輸入TEST時,主線程向子線程提供了兩個輸入,一個是來自鍵盤的輸入,一個來自主線程復數據到msg中,然后從運行結果可以看出,運行出現了異常,沒有處理和統計從鍵盤輸入TEST的字符串而卻對復制的數據作了兩次處理。原因如上面所述。

五、解決此缺陷的方法

解決方法有兩個,一個就是再增加一個信號量,讓主線程等到子線程處理統計完成之后再繼續執行;另一個方法就是使用互斥量。

下面給出用增加一個信號量的方法來解決該問題的代碼,源文件名為semthread2.c,源代碼如下:
[cpp]?view plain?copy
  1. #include?<unistd.h>??
  2. #include?<pthread.h>??
  3. #include?<semaphore.h>??
  4. #include?<stdlib.h>??
  5. #include?<stdio.h>??
  6. #include?<string.h>??
  7. ??
  8. ??
  9. //線程函數??
  10. void?*thread_func(void?*msg);??
  11. sem_t?sem;//信號量??
  12. sem_t?sem_add;//增加的信號量??
  13. ??
  14. ??
  15. #define?MSG_SIZE?512??
  16. ??
  17. ??
  18. int?main()??
  19. {??
  20. ????int?res?=?-1;??
  21. ????pthread_t?thread;??
  22. ????void?*thread_result?=?NULL;??
  23. ????char?msg[MSG_SIZE];??
  24. ????//初始化信號量,初始值為0??
  25. ????res?=?sem_init(&sem,?0,?0);??
  26. ????if(res?==?-1)??
  27. ????{??
  28. ????????perror("semaphore?intitialization?failed\n");??
  29. ????????exit(EXIT_FAILURE);??
  30. ????}??
  31. ????//初始化信號量,初始值為1??
  32. ????res?=?sem_init(&sem_add,?0,?1);??
  33. ????if(res?==?-1)??
  34. ????{??
  35. ????????perror("semaphore?intitialization?failed\n");??
  36. ????????exit(EXIT_FAILURE);??
  37. ????}??
  38. ????//創建線程,并把msg作為線程函數的參數??
  39. ????res?=?pthread_create(&thread,?NULL,?thread_func,?msg);??
  40. ????if(res?!=?0)??
  41. ????{??
  42. ????????perror("pthread_create?failed\n");??
  43. ????????exit(EXIT_FAILURE);??
  44. ????}??
  45. ????//輸入信息,以輸入end結束,由于fgets會把回車(\n)也讀入,所以判斷時就變成了“end\n”??
  46. ????printf("Input?some?text.?Enter?'end'to?finish...\n");??
  47. ??????
  48. ????sem_wait(&sem_add);??
  49. ????while(strcmp("end\n",?msg)?!=?0)??
  50. ????{??
  51. ????????if(strncmp("TEST",?msg,?4)?==?0)??
  52. ????????{??
  53. ????????????strcpy(msg,?"copy_data\n");??
  54. ????????????sem_post(&sem);??
  55. ????????????//把sem_add的值減1,即等待子線程處理完成??
  56. ????????????sem_wait(&sem_add);??
  57. ????????}??
  58. ????????fgets(msg,?MSG_SIZE,?stdin);??
  59. ????????//把信號量加1??
  60. ????????sem_post(&sem);??
  61. ????????//把sem_add的值減1,即等待子線程處理完成??
  62. ????????sem_wait(&sem_add);??
  63. ????}??
  64. ??
  65. ??
  66. ????printf("Waiting?for?thread?to?finish...\n");??
  67. ????//等待子線程結束??
  68. ????res?=?pthread_join(thread,?&thread_result);??
  69. ????if(res?!=?0)??
  70. ????{??
  71. ????????perror("pthread_join?failed\n");??
  72. ????????exit(EXIT_FAILURE);??
  73. ????}??
  74. ????printf("Thread?joined\n");??
  75. ????//清理信號量??
  76. ????sem_destroy(&sem);??
  77. ????sem_destroy(&sem_add);??
  78. ????exit(EXIT_SUCCESS);??
  79. }??
  80. ??
  81. ??
  82. void*?thread_func(void?*msg)??
  83. {??
  84. ????char?*ptr?=?msg;??
  85. ????//把信號量減1??
  86. ????sem_wait(&sem);??
  87. ????while(strcmp("end\n",?msg)?!=?0)??
  88. ????{??
  89. ????????int?i?=?0;??
  90. ????????//把小寫字母變成大寫??
  91. ????????for(;?ptr[i]?!=?'\0';?++i)??
  92. ????????{??
  93. ????????????if(ptr[i]?>=?'a'?&&?ptr[i]?<=?'z')??
  94. ????????????{??
  95. ????????????????ptr[i]?-=?'a'?-?'A';??
  96. ????????????}??
  97. ????????}??
  98. ????????printf("You?input?%d?characters\n",?i-1);??
  99. ????????printf("To?Uppercase:?%s\n",?ptr);??
  100. ????????//把信號量加1,表明子線程處理完成??
  101. ????????sem_post(&sem_add);??
  102. ????????//把信號量減1??
  103. ????????sem_wait(&sem);??
  104. ????}??
  105. ????sem_post(&sem_add);??
  106. ????//退出線程??
  107. ????pthread_exit(NULL);??
  108. }??
其運行結果如下:


分析:這里我們多使用了一個信號量sem_add,并把它的初值賦為1,在主線程在使用sem_wait來等待子線程處理完全,由于它的初值為1,所以主線程第一次調用sem_wait總是立即返回,而第二次調用則需要等待子線程處理完成之后。而在子線程中,若處理完成就會馬上使用sem_post來增加信號量的值,使主線程中的sem_wait馬上返回并執行緊接下面的代碼。從運行結果來看,運行終于正常了。注意,在線程函數中,信號量sem和sem_add使用sem_wait和sem_post函數的次序,它們的次序不能錯亂,否則在輸入end時,可能運行不正常,子線程不能正常退出,從而導致程序不能退出。

至于使用互斥量的方法,將會在下篇文章:Linux多線程——使用互斥量同步線程中詳細介紹。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/384220.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/384220.shtml
英文地址,請注明出處:http://en.pswp.cn/news/384220.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

linux下安裝erlang

1.安裝Erlang編譯依賴: yum -y install gcc glibc-devel make ncurses-devel openssl-devel xmlto perl wget 2.下載Erlang&#xff1a; wget http://www.erlang.org/download/otp_src_19.3.tar.gz 3.解壓并安裝 tar -xzvf otp_src_19.3.tar.gz cd otp_src_19.3 ./configure --…

Linux 線程同步的三種方法

http://blog.csdn.net/zsf8701/article/details/7844316 線程的最大特點是資源的共享性&#xff0c;但資源共享中的同步問題是多線程編程的難點。linux下提供了多種方式來處理線程同步&#xff0c;最常用的是互斥鎖、條件變量和信號量。 一、互斥鎖(mutex) 通過鎖機制實現線程…

Elixir特性

iex 退出&#xff1a;Ctrl-C 或Ctrl-G再輸入q 回車。 幫助文檔&#xff1a;h 查看輔函數列表 h IO 查看IO模塊幫助 h IO.puts 查看IO模塊中的puts函數的文檔 編譯和運行&#xff1a;創建一個hello.exs的文件。IO.puts "hello world"    //輸出hello world 使用el…

Elixir基礎

值類型 整數&#xff0c;包括十進制&#xff08;1234&#xff09;、十六進制&#xff08;0xcafe&#xff09;、八進制&#xff08;0o765&#xff09;和二進制&#xff08;0b1010&#xff09; 浮點數 原子&#xff0c;原子是常量&#xff0c;用于表現某些東西的名字&#xff0c;…

C++11新特性之八——函數對象function

http://www.cnblogs.com/yyxt/p/3987717.html 詳細請看《C Primer plus》(第六版中文版) http://www.cnblogs.com/lvpengms/archive/2011/02/21/1960078.html 備注&#xff1a; 函數對象&#xff1a; 盡管函數指針被廣泛用于實現函數回調&#xff0c;但C還提供了一個重要的實現…

分塊思想

今天學習了一個算法&#xff08;這個應該叫做算法吧&#xff1f;&#xff09;叫做分塊&#xff08;和莫隊&#xff0c;但是莫隊還沒有搞懂&#xff0c;搞懂再來寫吧&#xff09; 聽起來很高級&#xff0c;蒟蒻表示瑟瑟發抖。但是學完發現怎么那么像是一種變相的暴力呢。 分塊思…

從零開始學C++之STL(八):函數對象、 函數對象與容器、函數對象與算法

http://blog.csdn.net/jnu_simba/article/details/9500219 一、函數對象 1、函數對象&#xff08;function object&#xff09;也稱為仿函數&#xff08;functor&#xff09; 2、一個行為類似函數的對象&#xff0c;它可以沒有參數&#xff0c;也可以帶有若干參數。 3、任何重載…

樹狀數組初步理解

學習樹狀數組已經兩周了&#xff0c;之前偷懶一直沒有寫&#xff0c;趕緊補上防止自己忘記&#xff08;雖然好像已經忘得差不多了&#xff09;。 作為一種經常處理區間問題的數據結構&#xff0c;它和線段樹、分塊一樣&#xff0c;核心就是將區間分成許多個小區間然后通過對大區…

命名函數

函數體是代碼塊 代碼塊do...end是一種表達式的組織方式。 # ./times.exs下defmodule Times dodef doule(n) don * 2end end 函數調用與模式匹配 代碼如下&#xff1a; # ./factorial.exs    計算階層 defmodule Factorial dodef of(0), do: 1          #終止條件…

STL運用的C++技術(6)——函數對象

http://blog.csdn.net/wuzhekai1985/article/details/6658940?_t_t_t0.20427969420870595 STL是C標準庫的重要組成部分之一&#xff0c;它不僅是一個可復用的組件庫&#xff0c;更是一個包含算法與數據結構的軟件框架&#xff0c;同時也是C泛型編程的很好例子。STL中運用了許多…

列表與遞歸

頭部和尾部 [head | tail ] [1] #head 1 tail [] [head | tail ] [1, 2, 3] #head 1 tail [2, 3] [head | tail ] [] #報錯 創建映射函數 我們可以使用一個函數來處理列表中的各個元素&#xff0c;如此可以接受更加復雜的處理&#xff0c;也可以…

優先隊列小結

不像棧和隊列&#xff0c;雖然STL有較好實現但是我們自己也可以很方便的實現&#xff0c;優先隊列自己實現起來就比較復雜&#xff0c;比較浪費時間&#xff08;而且自己目前也不會233&#xff09;而優先隊列因為其較好的特性經常被使用&#xff0c;因此對它的熟練掌握是做題的…

字典:散列表、散列字典、關鍵字列表、集合與結構體

字典 散列表和散列字典都實現了Dict的行為。Keyword模塊也基本實現了&#xff0c;不同之處在于它支持重復鍵。 Eunm.into可以將一種類型的收集映射轉化成另一種。 defmodule Sum dodef values(dict) dodict |> Dict.values |> Enum.sumend endhd [ one: 1, two: 2, thre…

C++11 學習筆記 lambda表達式

http://blog.csdn.net/fjzpdkf/article/details/50249287 lambda表達式是C11最重要也最常用的一個特性之一。lambda來源于函數式編程的概念&#xff0c;也是現代編程語言的一個特點。 一.函數式編程簡介 定義&#xff1a;簡單說&#xff0c;“函數式編程”是一種“編程范式”。…

Cutting Codeforces Round #493 (Div. 2)

Cutting There are a lot of things which could be cut — trees, paper, “the rope”. In this problem you are going to cut a sequence of integers. There is a sequence of integers, which contains the equal number of even and odd numbers. Given a limited bud…

Enum、Stream

Enum 其常見用法見&#xff1a;https://cloud.tencent.com/developer/section/1116852 在sort時&#xff0c;如果要獲得穩定的排序結果&#xff0c;要使用< 而不是 <。 Stream Stream是延遲處理的&#xff0c;而Enum是貪婪的&#xff0c;則意味著傳給它一個收集&#xff…

linux網絡編程之posix 線程(三):posix 匿名信號量與互斥鎖 示例生產者--消費者問題

http://blog.csdn.net/jnu_simba/article/details/9123603 一、posix 信號量 信號量的概念參見這里。前面也講過system v 信號量&#xff0c;現在來說說posix 信號量。 system v 信號量只能用于進程間同步&#xff0c;而posix 信號量除了可以進程間同步&#xff0c;還可以線程間…

洛谷P1080-國王游戲-貪心+高精度

P1080-國王游戲 啊啊啊&#xff0c;剛才已經寫了一次了&#xff0c;但是Edge瀏覽器不知道為什么卡住了&#xff0c;難受。 好吧&#xff0c;其實是一道可做題&#xff0c;分析得到的貪心策略就是就是將a * b小的放在前面&#xff08;其他的懶得說了&#xff09;&#xff0c;主要…

字符串與二進制

單引號字符串會被表示成整數值列表。 &#xff1f;c返回字符 c 的整數編碼。下面這個例子用于解析字符列表表示法&#xff0c;該表示法用于表示一個任意的有符號的十進制數據。 defmodule Parse dodef number([ ?- | tail ]) do_number_digits(tail, 0) * -1enddef number([ ?…

P1092蟲食算-深度優先搜索+玄學剪枝

P1092蟲食算 這道題的思想并不復雜&#xff0c;可是難點在于各種玄學剪枝。在仔細研究了題解大佬的剪枝原理后終于氵了過去。 先上代碼&#xff1a; #include<cstdio> #include<cstring> #include<algorithm> using namespace std;const int MAXN100; int n…