訊號量 互斥鎖 條件變數的區別

來源:互聯網
上載者:User

 

訊號量用在多線程多任務同步的,一個線程完成了某一個動作就通過訊號量告訴別的線程,別的線程再進行某些動作(大家都在semtake的時候,就阻塞在哪裡)。而互斥鎖是用在多線程多任務互斥的,一個線程佔用了某一個資源,那麼別的線程就無法訪問,直到這個線程unlock,其他的線程才開始可以利用這個資源。比如對全域變數的訪問,有時要加鎖,操作完了,在解鎖。有的時候鎖和訊號量會同時使用的”
也就是說,訊號量不一定是鎖定某一個資源,而是流程上的概念,比如:有A,B兩個線程,B線程要等A線程完成某一任務以後再進行自己下面的步驟,這個任務並不一定是鎖定某一資源,還可以是進行一些計算或者資料處理之類。而線程互斥量則是“鎖住某一資源”的概念,在鎖定期間內,其他線程無法對被保護的資料進行操作。在有些情況下兩者可以互換。

兩者之間的區別:

範圍
訊號量: 進程間或線程間(linux僅線程間)
互斥鎖: 線程間

上鎖時
訊號量: 只要訊號量的value大於0,其他線程就可以sem_wait成功,成功後訊號量的value減一。若value值不大於0,則sem_wait阻塞,直到sem_post釋放後value值加一
互斥鎖: 只要被鎖住,其他任何線程都不可以訪問被保護的資源

成功後否則就阻塞

以下是號誌(量)的一些概念:

號誌與互斥鎖和條件變數的主要不同在於”燈”的概念,燈亮則意味著資源可用,燈滅則意味著不可用。如果說後兩中同步方式側重於”等待”操作,即資源停用話,號誌機制則側重於點燈,即告知資源可用;沒有等待線程的解鎖或激發條件都是沒有意義的,而沒有等待燈亮的線程的點燈操作則有效,且能保持燈亮狀態。當然,這樣的操作原語也意味著更多的開銷。

號誌的應用除了燈亮/燈滅這種二元燈以外,也可以採用大於1的燈數,以表示資源數大於1,這時可以稱之為多元燈。

1. 建立和 登出

POSIX號誌標準定義了有名號誌和無名號誌兩種,但LinuxThreads的實現僅有無名燈,同時有名燈除了總是可用於多進程之間以外,在使用上與無名燈並沒有很大的區別,因此下面僅就無名燈進行討論。

int sem_init(sem_t *sem, int pshared, unsigned int value)
這是建立號誌的API,其中value為號誌的初值,pshared表示是否為多進程共用而不僅僅是用於一個進程。LinuxThreads沒有實現多進程共用號誌,因此所有非0值的pshared輸入都將使sem_init()返回-1,且置errno為ENOSYS。初始化好的號誌由sem變數表徵,用於以下點燈、滅燈操作。

int sem_destroy(sem_t * sem)
被登出的號誌sem要求已沒有線程在等待該號誌,否則返回-1,且置errno為EBUSY。除此之外,LinuxThreads的號誌 登出函數不做其他動作。

2. 點燈和滅燈

int sem_post(sem_t * sem)

點燈操作將號誌值原子地加1,表示增加一個可訪問的資源。

int sem_wait(sem_t * sem)
int sem_trywait(sem_t * sem)

sem_wait()為等待燈亮操作,等待燈亮(號誌值大於0),然後將號誌原子地減1,並返回。sem_trywait()為sem_wait()的非阻塞版,如果號誌計數大於0,則原子地減1並返回0,否則立即返回-1,errno置為EAGAIN。

3. 擷取燈值

int sem_getvalue(sem_t * sem, int * sval)

讀取sem中的燈計數,存於*sval中,並返回0。

4. 其他

sem_wait()被實現為取消點,而且在支援原子”比較且交換”指令的體繫結構上,sem_post()是唯一能用於非同步訊號處理函數的POSIX非同步訊號 安全的API。

----------------------------
線程同步:何時互斥鎖不夠,還需要條件變數?

假設有共用的資源sum,與之相關聯的mutex 是lock_s.假設每個線程對sum的操作很簡單的,與sum的狀態無關,比如只是sum++.那麼只用mutex足夠了.程式員只要確保每個線程操作前,取得lock,然後sum++,再unlock即可.每個線程的代碼將像這樣
add()
{
pthread_mutex_lock(lock_s);
sum++;
pthread_mutex_unlock(lock_s);
}

  如果操作比較複雜,假設線程t0,t1,t2的操作是sum++,而線程t3則是在sum到達100的時候,列印出一條資訊,並對sum清零. 這種情況下,如果只用mutex, 則t3需要一個迴圈,每個迴圈裡先取得lock_s,然後檢查sum的狀態,如果sum>=100,則列印並清零,然後unlock.如果sum& amp; lt;100,則unlock,並sleep()本線程合適的一段時間.

 這個時候,t0,t1,t2的代碼不變,t3的代碼如下
print()
{
while (1)
{
pthread_mutex_lock(lock_s);
if(sum<100)
{
printf(“sum reach 100!”);
pthread_mutex_unlock(lock_s);
}
else
{
pthread_mutex_unlock(lock_s);
my_thread_sleep(100);
return OK;
}
}
}

這種辦法有兩個問題
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()
{
pthread_mutex_lock(lock_s);
sum++;
pthread_mutex_unlock(lock_s);
if(sum>=100)
pthread_cond_signal(&cond_sum_ready);
}
而t3的代碼則是
print
{
pthread_mutex_lock(lock_s);
while(sum<100)
pthread_cond_wait(&cond_sum_ready, &lock_s);
printf(“sum is over 100!”);
sum=0;
pthread_mutex_unlock(lock_s);
return OK;
}

注意兩點:
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的用意

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.