Posix線程 它們那一大家子事兒,要覺得好你就收藏進被窩慢慢看(2)
接上一篇 Posix線程(1)
文章目錄
①線程同步
線程為什么要同步?
②互斥鎖
互斥量原語
參數釋義
互斥量使用
死鎖
③條件變量
條件變量原語
條件變量與互斥鎖
注意事項
虛假喚醒與喚醒丟失
⑴虛假喚醒
⑵喚醒丟失
使用條件變量
③線程池
④Pthread API函數
①線程同步
線程為什么要同步?
做個小實驗吧,兩個線程計數。如果最后加起來是20萬那就不用往下看了。
#include
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
好,為什么要線程同步,那就心照不宣了
算了,官方話還是要說一說的
1、共享資源,多個線程都可以對共享資源進行操作
2、線程操作共享資源的先后順序不一定
3、處理器對存儲器的操作一般不是原子操作
②互斥鎖
什么叫互斥量,顧名思義就是咱這么多人,只能有一個使用這個資源,就像共享小單車,一次只能給一個人用,一個人下車鎖車了,另一個人才能去掃碼開鎖。
互斥量原語
pthread_mutex_t mutex = PTHREAD_MUREX_INITALIZER //用于初始化互斥鎖,后面簡稱鎖 int pthread_mutex_init(pthread_mutex_t *restrict mutex,const pthread_mutexattr_t *restrict attr); //初始化鎖,和上面那個一個意思。 //初始化一個互斥鎖(互斥量)–>初值可看做1 int pthread_mutex_destroy(pthread_mutex_t *mutex); //銷毀鎖 int pthread_mutex_lock(pthread_mutex_t *mutex); //上鎖 int pthread_mutex_unlok(pthread_mutex_t *mutex); //解鎖 int pthread_mutex_trylock(pthread_mutex_t *mutex); //嘗試上鎖
1
2
3
4
5
6
7
8
參數釋義
<這里只釋義那個init>
參數1:傳出參數,調用時應傳&mutex
restrict關鍵字:只用于限制指針,告訴編譯器,所有修改該指針指向內存中內容的操作,只能通過本指針完成。不能通過除本指針以外的其他變量或指針修改。
參數2:互斥屬性。是一個傳入參數,通常傳NULL,選用默認屬性(線程間共享).
靜態初始化:如果互斥鎖mutex是靜態分配的(定義在全局,或加了static關鍵字修飾),可以直接使用宏進行初始化。pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
動態初始化:局部變量應采用動態初始化。pthread_mutex_init(&mutex, NULL);
attr對象用于設置互斥量對象的屬性,使用時必須聲明為pthread_mutextattr_t類型,默認值可以是NULL。Pthreads標準定義了三種可選的互斥量屬性:
協議(Protocol): 指定了協議用于阻止互斥量的優先級改變
優先級上限(Prioceiling):指定互斥量的優先級上限
進程共享(Process-shared):指定進程共享互斥量
注意所有實現都提供了這三個可選的互斥量屬性。
Q:有多個線程等待同一個鎖定的互斥量,當互斥量被解鎖后,那個線程會第一個鎖定互斥量? A:除非線程使用了優先級調度機制,否則,線程會被系統調度器去分配,那個線程會第一個鎖定互斥量是隨機的。
1
2
3
互斥量使用
#include
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
拿去執行,如果不是20萬也可以不用往下看了。
死鎖
為什么我要強調上鎖和解鎖一定要放在一起寫,就是防止出現人為失誤導致死鎖
死鎖嘛,解不開了。
要么是你忘了解開,別人也就沒得用了
要么就是幾個線程互相掐著關鍵數據導致誰也沒辦法完成任務,結果誰也沒辦法解鎖。
這種情況下只有銷毀掉代價最小的那個鎖,讓任務執行下去,不過后面要記得把那個被銷毀的任務重新運作。
③條件變量
條件變量提供了另一種同步的方式?;コ饬客ㄟ^控制對數據的訪問實現了同步,而條件變量允許根據實際的數據值來實現同步。
沒有條件變量,程序員就必須使用線程去輪詢(可能在臨界區),查看條件是否滿足。這樣比較消耗資源,因為線程連續繁忙工作。條件變量是一種可以實現這種輪詢的方式。
條件變量往往和互斥一起使用
使用條件變量的代表性順序如下:
條件變量原語
//初始化條件變量: //本人還是喜歡靜態初始化,省事兒 pthread_cont_t cont = PTHREAD_COND_INITIALIZER; //好,再看看動態初始化 int pthread_cond_init(pthread_cond_t *cond, const pthread_condattr_t *attr); //參數釋義:cond:用于接收初始化成功管道條件變量 //attr:通常為NULL,且被忽略 //有初始化那肯定得有銷毀 int pthread_cond_destroy(pthread_cond_t *cond); //既然說條件變量是用來等待的,那就更要看看這等待的特殊之處了 int pthread_cond_wait(pthread_cond_t *cond,pthread_mutex_t *mutex); //無條件等待 int pthread_cond_timedwait(pthread_cond_t *cond,pthread_mutex_t mytex,const struct timespec *abstime); //計時等待 //好,加入等待喚醒大軍了,那得看看怎么去喚醒了 int pthread_cond_signal(pthread_cond_t *cptr); //喚醒一個等待該條件的線程。存在多個線程是按照其隊列入隊順序喚醒其中一個 int pthread_cond_broadcast(pthread_cond_t * cptr); //廣播,喚醒所喲與等待線程
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
條件變量與互斥鎖
在服務器編程中常用的線程池,多個線程會操作同一個任務隊列,一旦發現任務隊列中有新的任務,子線程將取出任務;這里因為是多線程操作,必然會涉及到用互斥鎖保護任務隊列的情況(否則其中一個線程操作了任務隊列,取出線程到一半時,線程切換又取出相同任務)。但是互斥鎖一個明顯的缺點是它只有兩種狀態:鎖定和非鎖定。設想,每個線程為了獲取新的任務不斷得進行這樣的操作:鎖定任務隊列,檢查任務隊列是否有新的任務,取得新的任務(有新的任務)或不做任何操作(無新的任務),釋放鎖,這將是很消耗資源的。
而條件變量通過允許線程阻塞和等待另一個線程發送信號的方法彌補了互斥鎖的不足,它常和互斥鎖一起配合使用。使用時,條件變量被用來阻塞一個線程,當條件不滿足時,線程往往解開相應的互斥鎖并等待條件發生變化。一旦其他的某個線程改變了條件變量,他將通知相應的條件變量喚醒一個或多個正被此條件變量阻塞的線程。這些線程將重新鎖定互斥鎖并重新測試條件是否滿足。一般說來,條件變量被用來進行線程間的同步。對應于線程池的場景,我們可以讓線程處于等待狀態,當主線程將新的任務放入工作隊列時,發出通知(其中一個或多個),得到通知的線程重新獲得鎖,取得任務,執行相關操作。
注意事項
(1)必須在互斥鎖的保護下喚醒,否則喚醒可能發生在鎖定條件變量之前,照成死鎖。
(2)喚醒阻塞在條件變量上的所有線程的順序由調度策略決定
(3)如果沒有線程被阻塞在調度隊列上,那么喚醒將沒有作用。
(4)以前不懂事兒,就喜歡廣播。由于pthread_cond_broadcast函數喚醒所有阻塞在某個條件變量上的線程,這些線程被喚醒后將再次競爭相應的互斥鎖,所以必須小心使用pthread_cond_broadcast函數。
虛假喚醒與喚醒丟失
⑴虛假喚醒
在多核處理器下,pthread_cond_signal可能會激活多于一個線程(阻塞在條件變量上的線程)。結果是,當一個線程調用pthread_cond_signal()后,多個調用pthread_cond_wait()或pthread_cond_timedwait()的線程返回。這種效應成為”虛假喚醒”(spurious wakeup)
Linux幫助里面有
為什么不去修正,性價比不高嘛。
所以通常的標準解決辦法是這樣的:
⑵喚醒丟失
無論哪種等待方式,都必須和一個互斥量配合,以防止多個線程來打擾。
互斥鎖必須是普通鎖或適應鎖,并且在進入pthread_cond_wait之前必須由本線程加鎖。
在更新等待隊列前,mutex必須保持鎖定狀態. 在線程進入掛起,進入等待前,解鎖。(好繞啊,我已經盡力斷句了)
在條件滿足并離開pthread_cond_wait前,上鎖。以恢復它進入cont_wait之前的狀態。
為什么等待會被上鎖?
以免出現喚醒丟失問題。 這里有個大神解釋要不要看:https://stackoverflow.com/questions/4544234/calling-pthread-cond-signal-without-locking-mutex 做事做全套,源碼也給放這兒了:https://code.woboq.org/userspace/glibc/nptl/pthread_cond_wait.c.html
1
2
3
在放些咱能看懂的中文解釋:將線程加入喚醒隊列后方可解鎖。保證了線程在陷入wait后至被加入喚醒隊列這段時間內是原子的。
但這種原子性依賴一個前提條件:喚醒者在調用pthread_cond_broadcast或pthread_cond_signal喚醒等待者之前也必須對相同的mutex加鎖。
滿足上述條件后,如果一個等待事件A發生在喚醒事件B之前,那么A也同樣在B之前獲得了mutex,那A在被加入喚醒隊列之前B都無法進入喚醒調用,因此保證了B一定能夠喚醒A;試想,如果A、B之間沒有mutex來同步,雖然B在A之后發生,但是可能B喚醒時A尚未被加入到喚醒隊列,這便是所謂的喚醒丟失。
在線程未獲得相應的互斥鎖時調用pthread_cond_signal或pthread_cond_broadcast函數可能會引起喚醒丟失問題。 喚醒丟失往往會在下面的情況下發生: 一個線程調用pthread_cond_signal或pthread_cond_broadcast函數; 另一個線程正處在測試條件變量和調用pthread_cond_wait函數之間; 沒有線程正在處在阻塞等待的狀態下。
1
2
3
4
5
6
7
使用條件變量
//例子演示了使用Pthreads條件變量的幾個函數。主程序創建了三個線程,兩個線程工作,根系“count”變量。第三個線程等待count變量值達到指定的值。 #include 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 ③線程池 線程池 ④Pthread API函數 任務調度
版權聲明:本文內容由網絡用戶投稿,版權歸原作者所有,本站不擁有其著作權,亦不承擔相應法律責任。如果您發現本站中有涉嫌抄襲或描述失實的內容,請聯系我們jiasou666@gmail.com 處理,核實后本網站將在24小時內刪除侵權內容。
版權聲明:本文內容由網絡用戶投稿,版權歸原作者所有,本站不擁有其著作權,亦不承擔相應法律責任。如果您發現本站中有涉嫌抄襲或描述失實的內容,請聯系我們jiasou666@gmail.com 處理,核實后本網站將在24小時內刪除侵權內容。