線程同步:互斥鎖,條件變量,信號量


線程同步

為允許在線程或進程間共享數據,同步通常是必須的。常見的同步方式有:互斥鎖、條件變量、讀寫鎖、信號量。另外,對於進程間的同步,也可以通過進程間通信的方式進行同步,包括管道(無名管道、有名管道)、信號量、消息隊列、共享內存、遠程過程調用,當然也可以通過Socket來進行網絡控制。

一.  互斥鎖和條件變量是同步的基本組成部分

  互斥鎖和條件變量出自Posix.1線程標准,多用來同步一個進程中各個線程。但如果將二者存放在多個進程間共享的內存區中,它們也可以用來進行進程間的同步。

1. 互斥鎖

  用於保護臨界區,以保護任何時刻只有一個線程在執行其中的代碼,其大體輪廓大體如下:

  lock_the_mutex(...);

  臨界區

  unlock_the_mutex(...);

  下列三個函數給一個互斥鎖上鎖和解鎖:

  #include <pthread.h>

  int pthread_mutex_lock(pthread_mutex_t *mptr);  //若不能立刻獲得鎖,將阻塞在此處

  int pthread_mutex_trylock(pthread_mutex_t *mptr);  //若不能立刻獲得鎖,將返回EBUSY,用戶可以根據此返回值做其他操作,非阻塞模式

  int pthread_mutex_unlock(pthread_mutex_t *mptr);  //釋放鎖

  互斥鎖通常用於保護由多個線程或多個進程分享的共享數據(Share Data)

2.  條件變量(線程之間同步)

  它是發送信號與等待信號。互斥鎖用戶上鎖,條件變量則用於等待。一般來說,在一個進程/線程中調用pthread_cond_wait(..)等待某個條件的成立,此時該進程阻塞在這里,另外一個進程/線程進行某種操作,當某種條件成立時,調用pthread_cond_signal(...)來發送信號,從而使pthread_cond_wait(...)返回。此處要注意的是,這里所談到的信號,不是系統級別的SIGXXXX信號,只是用信號這個詞語更容易理解。條件變量與信號量更接近或者就可以認為是信號量。

  下列兩個函數用來對條件變量進行控制:

  #include <pthread.h>

  int pthread_cond_wait(pthread_cond_t *cptr, pthread_mutex_t *mptr);

  int pthread_cond_signal(pthread_cond_t *cptr);

  由代碼我們可以看出,條件變量的使用是需要結合鎖機制的,即上面所提到的互斥鎖。也就是說,一個進程/線程要等到臨界區的共享數據達到某種狀態時再進行某種操作,而這個狀態的成立,則是由另外一個進程/線程來完成后發送信號來通知的。

  其實想一想,pthread_cond_wait函數也可以用一個while死循環來等待條件的成立,但要注意的是,使用while死循環會嚴重消耗CPU,而pthread_cond_wait則是采用線程睡眠的方式,它是一種等待模式,而不是一直的檢查模式。

  總的來說,給條件變量發送信號的代碼大體如下:

  pthread_mutex_lock(&mutex);

  設置條件為真

  pthread_mutex_unlock(&mutex);  

      pthread_cond_signal(&cond);  //發送信號

  等待條件並進入睡眠以等待條件變為真的代碼大體如下:

  pthread_mutex_lock(&mutex); 

  while(條件為假)

    pthread_cond_wait(&cond,&mutex);  

  執行某種操作

  pthread_mutex_unlock(&mutex);

  在這里需要注意的是,pthread_cond_wait(&cond,&mutex)是一個原子操作,當它執行時,首先對mutex解鎖,這樣另外的線程才能得到鎖來修改條件,pthread_cond_wait解鎖后,再將本身的線程/進程投入睡眠,另外,當該函數返回時,會再對mutex進行加鎖,這樣才能“執行某種操作”后unlock鎖

 

二、 信號量

  英文:semaphore,它是一種專門用於提供不同進程間或線程間同步手段的原語。可以通過下圖來理解它。

        進程A                               進程B

          \          /

     進程    \         /

        ----------------------------------------------------

     內核      \      /

              信號量

  也就是說,信號量是由內核來維護的,他獨立出進程。因此可以通過它來進行同步

  上圖一般來說,是基於Posix有名信號量,可以認為它是系統中的一個特殊文件(因為在Linux中,一切都可以認為是文件),因為在進程間的通信、同步中用的比較多,如果是線程之間的同步,經常用基於Posix內存的信號量。(基於內存的信號量必須在創建時指定是否在進程間共享,有名信號量隨內核有持續性,需手工刪除,而基於內存的信號量具有隨進程的持續性)

  對於信號量的工作原理,其實和互斥鎖+條件變量相似

  主要函數有:sem_open、sem_close、sem_unlink,這里要注意,close只是關閉信號量,但並未從系統中刪除,而unlink是刪除該信號量。

  sem_wait和sem_trywait函數,他們和pthread_cond_wait功能相似,都是等待某個條件的成立,sem_wait和sem_trywait的區別是,當所指定的信號量的值為0時,后者並不將調用者投入睡眠,而是立刻返回EAGAIN,即重試。

  sem_post和sem_getvalue函數,sem_post將指定的信號量加一,然后喚醒正在等待該信號量值變為正數的任意線程。sem_getvalue是用來獲取當前信號量值的函數。

 

三、互斥鎖、條件變量、信號量三者的差別:

  (1) 互斥鎖必須總是由給他上鎖的線程解鎖(因為此時其他線程根本得不到此鎖),信號量沒有這種限制:一個線程等待某個信號量,而另一個線程可以掛出該信號量

  (2)每個信號量有一個與之關聯的值,掛出時+1,等待時-1,那么任何線程都可以掛出一個信號,即使沒有線程在等待該信號量的值。不過對於條件變量來說,如果pthread_cond_signal之后沒有任何線程阻塞在pthread_cond_wait上,那么此條件變量上的信號丟失。

  (3)在各種各樣的同步技巧中,能夠從信號處理程序中安全調用的唯一函數是sem_post

作用域
  信號量: 進程間或線程間(linux僅線程間的無名信號量pthread semaphore)
  互斥鎖: 線程間

上鎖時 
  信號量: 只要信號量的value大於0,其他線程就可以sem_wait成功,成功后信號量的value減一。若value值不大於0,則sem_wait使得線程阻塞,直到sem_post釋放后value值加一,但是sem_wait返回之前還是會將此value值減一
  互斥鎖: 只要被鎖住,其他任何線程都不可以訪問被保護的資源

 

四、基本概念

1、Mutex(互斥量)

  互斥鎖(Mutex,Mutual Exclusive Lock),獲得鎖的線程可以完成“讀-修改-寫”的操作,然后釋放鎖給其它線程,沒有獲得鎖的線程只能等待而不能訪問共享數據,這樣“讀-修改-寫”三步操作組成一個原子操作

  Mutex用pthread_mutex_t類型的變量表示。

  對Mutex變量的讀取、判斷和修改不是原子操作。如果兩個線程同時調用lock,這時Mutex是1,兩個線程都判斷mutex>0成立,然后其中一個線程置mutex=0,而另一個線程並不知道這一情況,也置mutex=0,於是兩個線程都以為自己獲得了鎖。

  為了實現互斥鎖操作,大多數體系結構都提供了swapexchange指令,該指令的作用是把寄存器和內存單元的數據相交換,由於只有一條指令,保證了原子性,即使是多處理器平台,訪問內存的總線周期也有先后,一個處理器上的交換指令執行時另一個處理器的交換指令只能等待總線周期。

 

2、Condition Variable(條件變量)

  線程間的同步還有這樣一種情況:線程A需要等某個條件成立才能繼續往下執行,現在這個條件不成立,線程A就阻塞等待,而線程B在執行過程中使這個條件成立了,就喚醒線程A繼續執行。在pthread庫中通過條件變量(Condition Variable)來阻塞等待一個條件,或者喚醒等待這個條件的線程。Condition Variable用pthread_cond_t類型的變量表示。

  一個Condition Variable總是和一個Mutex搭配使用的。一個線程可以調用pthread_cond_wait在一個Condition Variable上阻塞等待。

  采用條件變量控制線程同步,最為經典的例子要數生產者和消費者問題。

 

3、semaphore(信號量)

  很顯然,pthread中的條件變量與Java中的wait,notify類似

  Mutex變量是非0即1的,可看作一種資源的可用數量,初始化時Mutex是1,表示有一個可用資源,加鎖時獲得該資源,將Mutex減到0,表示不再有可用資源,解鎖時釋放該資源,將Mutex重新加到1,表示又有了一個可用資源。

  信號量(Semaphore)和Mutex類似,表示可用資源的數量,和Mutex不同的是這個數量可以大於1。

  本文介紹的是POSIX semaphore庫函數),這種信號量不僅可用於同一進程的線程間同步,也可用於不同進程間的同步。

  semaphore變量的類型為sem_t,sem_init()初始化一個semaphore變量,value參數表示可用資源的數量,pshared參數為0表示信號量用於同一進程的線程間同步,本節只介紹這種情況。在用完semaphore變量之后應該調用sem_destroy()釋放與semaphore相關的資源。

  調用sem_wait()可以獲得資源,使semaphore的值減1,如果調用sem_wait()時semaphore的值已經是0,則掛起等待。如果不希望掛起等待,可以調用sem_trywait()。調用sem_post()可以釋放資源,使semaphore的值加1,同時喚醒掛起等待的線程。

本質上,信號量實現了互斥量+條件變量的功能

 

 

Reference

線程同步、條件變量、互斥鎖的使用

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

http://islub.diandian.com/post/2012-06-28/40042085746


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM