1. 何時Sem不夠,還需要使用Mutex?
假設有共用的資源sum,與之相關聯的mutex 是lock_s.假設每個線程對sum的操作很簡單的,與sum的狀態無關,比如只是sum++.那麼只用mutex足夠了.程式員只要確保每個線程操作前,取得lock,然後sum++,再unlock即可.每個線程的代碼將像這樣:
add()<br />{<br /> pthread_mutex_lock(lock_s);<br /> sum++;<br /> pthread_mutex_unlock(lock_s);<br />}
如果操作比較複雜,假設線程t0,t1,t2的操作是sum++,而線程t3則是在sum到達100的時候,列印出一條資訊,並對sum清零. 這種情況下,如果只用mutex, 則t3需要一個迴圈,每個迴圈裡先取得lock_s,然後檢查sum的狀態,如果sum>=100,則列印並清零,然後unlock.如果sum& lt;100,則unlock,並sleep()本線程合適的一段時間.這個時候,t0,t1,t2的代碼不變,t3的代碼如下:
print()<br />{<br /> while (1)<br /> {<br /> pthread_mutex_lock(lock_s);<br /> if(sum<100)<br /> {<br />printf(“sum reach 100!”);<br />pthread_mutex_unlock(lock_s);<br /> }<br /> else<br /> {<br />pthread_mutex_unlock(lock_s);<br />my_thread_sleep(100);<br />return OK;<br /> }<br /> }<br />}
這種辦法有兩個問題
1) sum在大多數情況下不會到達100,那麼對t3的代碼來說,大多數情況下,走的是else分支,只是lock和unlock,然後sleep().這浪費了CPU處理時間.
2) 為了節省CPU處理時間,t3會在探測到sum沒到達100的時候sleep()一段時間.這樣卻又帶來另外一個問題,亦即t3響應速度下降.可能在sum到達200的時候,t4才會醒過來.
3) 這樣,程式員在設定sleep()時間的時候陷入兩難境地,設定得太短了節省不了資源,太長了又降低響應速度.真是難辦啊!
這個時候,condition variable內褲外穿,從天而降,拯救了焦頭爛額的你!
你首先定義一個condition variable,
pthread_cond_t cond_sum_ready=PTHREAD_COND_INITIALIZER;
t0,t1,t2的代碼只要後面加兩行,像這樣:
add()<br />{<br /> pthread_mutex_lock(lock_s);<br /> sum++;<br /> pthread_mutex_unlock(lock_s);<br /> if(sum>=100)<br /> pthread_cond_signal(&cond_sum_ready);<br />}
而t3的代碼變成了:
print<br />{<br /> pthread_mutex_lock(lock_s);<br /> while(sum<100)<br />pthread_cond_wait(&cond_sum_ready, &lock_s);<br /> printf(“sum is over 100!”);<br /> sum=0;<br /> pthread_mutex_unlock(lock_s);<br /> return OK;<br />}
注意兩點:
1) 在thread_cond_wait()之前,必須先lock相關聯的mutex, 因為假如目標條件未滿足,pthread_cond_wait()實際上會unlock該mutex, 然後block,在目標條件滿足後再重新lock該mutex, 然後返回.
2) 為什麼是while(sum<100),而不是if(sum<100) ?這是因為在pthread_cond_signal()和pthread_cond_wait()返回之間,有時間差,假設在這個時間差內,還有另外一 個線程t4又把sum減少到100以下了,那麼t3在pthread_cond_wait()返回之後,顯然應該再檢查一遍sum的大小.這就是用 while的作用.
其它:
x86原子操作指令的說明
cmpxchg 比較交換指令,其語義為:
int CompareAndExchange(int *ptr, int old, int new)<br />{<br /> int actual = *ptr;<br /> if(actual == old)<br /> *ptr = new;<br /> return actual;<br />}
Inter白皮書上面的說明:
(* Accumulator = AL, AX, EAX, or RAX depending on whether a byte, word, doubleword, or<br /> quadword comparison is being performed *)<br /> IF accumulator = DEST<br /> THEN<br /> ZF ← 1;<br /> DEST ← SRC;<br /> ELSE<br /> ZF ← 0;<br /> accumulator ← DEST;<br /> FI;
使用此原子操作可以實現自旋鎖,之前有一篇文章中描述了實現:
void lock(lock_t *lock) {<br /> while (CompareAndExchange(&lock->flag, 0, 1) == 1)<br /> ; // spin<br /> }<br /> void unlock(lock_t *lock) {<br /> lock->flag = 0;<br /> }
關於smp下的原子操作的一些說明:
原子操作是不可分割的,在執行完畢不會被任何其它任務或事件中斷。在單一處理器系統(UniProcessor)中,能夠在單條指令中完成的操作都可以認為是" 原子操作",因為中斷只能發生於指令之間。這也是某些CPU指令系統中引入了test_and_set、test_and_clear等指令用於臨界資源互斥的原因。在對稱式多處理器(Symmetric Multi-Processor)結構中就不同了,由於系統中有多個處理器在獨立地運行,即使能在單條指令中完成的操作也有可能受到幹擾。
在x86 平台上,CPU提供了在指令執行期間對匯流排加鎖的手段。CPU晶片上有一條引線#HLOCK pin,如果組合語言的程式中在一條指令前面加上首碼"LOCK",經過彙編以後的機器代碼就使CPU在執行這條指令的時候把#HLOCK pin的電位拉低,持續到這條指令結束時放開,從而把匯流排鎖住,這樣同一匯流排上別的CPU就暫時不能通過匯流排訪問記憶體了,保證了這條指令在多處理器環境中的原子性。
當然,並不是所有的指令前面都可以加lock首碼的,只有ADD, ADC, AND, BTC, BTR, BTS, CMPXCHG,DEC, INC, NEG, NOT, OR, SBB, SUB, XOR, XADD, 和 XCHG指令前面可以加lock指令,實現原子操作。
再其它:
自旋鎖:一直去請求資源,在此過程中cpu不能去調度別的任務去處理,因此是損耗cpu的.
互斥鎖:先去請求資源,如果失敗了,立刻進行環境切換,此時CPU可以有時間去調度別的任務進行處理.
本文參考了以下部落格的內容,在此表示感謝!
http://www.eetop.cn/blog/html/04/343504-14125.html
http://dev.firnow.com/course/6_system/linux/Linuxjs/20090901/173322.html