信號(hào)量強(qiáng)調(diào)的是線程(或進(jìn)程)間的同步:“信號(hào)量用在多線程多任務(wù)同步的,一個(gè)線程完成了某一個(gè)動(dòng)作就通過(guò)信號(hào)量告訴別的線程,別的線程再進(jìn)行某些動(dòng)作(大家都
在sem_wait的時(shí)候,就阻塞在那里)。當(dāng)信號(hào)量為單值信號(hào)量是,也可以完成一個(gè)資源的互斥訪問(wèn)。
有名信號(hào)量:可以用于不同進(jìn)程間或多線程間的互斥與同步
創(chuàng)建打開(kāi)有名信號(hào)量
sem_t *sem_open(const char *name, int oflag);
sem_t *sem_open(const char *name, int oflag, mode_t mode, unsigned int value);
成功返回信號(hào)量指針;失敗返回SEM_FAILED,設(shè)置errno
name是文件路徑名,但不能寫(xiě)成/tmp/a.sem這樣的形式,因?yàn)樵趌inux下,sem都是在/dev/shm目錄下,可寫(xiě)成"/mysem"或"mysem",創(chuàng)建出來(lái)的文件都
是"/dev/shm/sem.mysem",mode設(shè)置為0666,value設(shè)置為信號(hào)量的初始值.所需信號(hào)燈等已存在條件下指定
O_CREAT|O_EXCL卻是個(gè)錯(cuò)誤。
關(guān)閉信號(hào)量,進(jìn)程終止時(shí),會(huì)自動(dòng)調(diào)用它
int sem_close(sem_t *sem);
成功返回0;失敗返回-1,設(shè)置errno
刪除信號(hào)量,立即刪除信號(hào)量名字,當(dāng)其他進(jìn)程都關(guān)閉它時(shí),銷(xiāo)毀它
int sem_unlink(const char *name);
等待信號(hào)量,測(cè)試信號(hào)量的值,如果其值小于或等于0,那么就等待(阻塞);一旦其值變?yōu)榇笥?就將它減1,并返回
int sem_wait(sem_t *sem);
int sem_trywait(sem_t *sem);
成功返回0;失敗返回-1,設(shè)置errno
當(dāng)信號(hào)量的值為0時(shí),sem_trywait立即返回,設(shè)置errno為EAGAIN。如果被某個(gè)信號(hào)中斷,sem_wait會(huì)過(guò)早地返回,設(shè)置errno為EINTR
發(fā)出信號(hào)量,給它的值加1,然后喚醒正在等待該信號(hào)量的進(jìn)程或線程
int sem_post(sem_t *sem);
成功返回0;失敗返回-1,不會(huì)改變它的值,設(shè)置errno,該函數(shù)是異步信號(hào)安全的,可以在信號(hào)處理程序里調(diào)用它
無(wú)名信號(hào)量,用于進(jìn)程體內(nèi)各線程間的互斥和同步,使用如下API(無(wú)名信號(hào)量,基于內(nèi)存的信號(hào)量)
(1)、sem_init
功能:用于創(chuàng)建一個(gè)信號(hào)量,并初始化信號(hào)量的值。
頭文件:
函數(shù)原型: int sem_init (sem_t* sem, int pshared, unsigned int value);
函數(shù)傳入值: sem:信號(hào)量。pshared:決定信號(hào)量能否在幾個(gè)進(jìn)程間共享。由于目前LINUX還沒(méi)有實(shí)現(xiàn)進(jìn)程間共享信息量,所以這個(gè)值只能取0。
(2)其他函數(shù)。
int sem_wait (sem_t* sem);
int sem_trywait (sem_t* sem);
int sem_post (sem_t* sem);
int sem_getvalue (sem_t* sem);
int sem_destroy (sem_t* sem);
功能:sem_wait和sem_trywait相當(dāng)于P操作,它們都能將信號(hào)量的值減一,兩者的區(qū)別在于若信號(hào)量的值小于零時(shí),sem_wait將會(huì)阻塞進(jìn)程,而sem_trywait則會(huì)立即返回
。sem_post相當(dāng)于V操作,它將信號(hào)量的值加一,同時(shí)發(fā)出喚醒的信號(hào)給等待
的進(jìn)程(或線程)。
sem_getvalue 得到信號(hào)量的值。
sem_destroy 摧毀信號(hào)量。
如果某個(gè)基于內(nèi)存的信號(hào)燈是在不同進(jìn)程間同步的,該信號(hào)燈必須存放在共享內(nèi)存區(qū)中,這要只要該共享內(nèi)存區(qū)存在,該信號(hào)燈就存在。
互斥鎖(又名互斥量)強(qiáng)調(diào)的是資源的訪問(wèn)互斥:互斥鎖是用在多線程多任務(wù)互斥的,一個(gè)線程占用了某一個(gè)資源,那么別的線程就無(wú)法訪問(wèn),直到這個(gè)線程unlock,其他的
線程才開(kāi)始可以利用這個(gè)資源。比如對(duì)全局變量的訪問(wèn),有時(shí)要加鎖,操作完了,在解鎖。有的時(shí)候鎖和信號(hào)量會(huì)同時(shí)使用的”
也就是說(shuō),信號(hào)量不一定是鎖定某一個(gè)資源,而是流程上的概念,比如:有A,B兩個(gè)線程,B線程要等A線程完成某一任務(wù)以后再進(jìn)行自己下面的步驟,這個(gè)任務(wù)并不一定是鎖
定某一資源,還可以是進(jìn)行一些計(jì)算或者數(shù)據(jù)處理之類(lèi)。而線程互斥量則是“鎖住某一資源”的概念,在鎖定期間內(nèi),其他線程無(wú)法對(duì)被保護(hù)的數(shù)據(jù)進(jìn)行操作。在有些情況
下兩者可以互換。
在linux下, 線程的互斥量數(shù)據(jù)類(lèi)型是pthread_mutex_t. 在使用前, 要對(duì)它進(jìn)行初始化:
對(duì)于靜態(tài)分配的互斥量, 可以把它設(shè)置為PTHREAD_MUTEX_INITIALIZER, 或者調(diào)用pthread_mutex_init.
對(duì)于動(dòng)態(tài)分配的互斥量, 在申請(qǐng)內(nèi)存(malloc)之后, 通過(guò)pthread_mutex_init進(jìn)行初始化, 并且在釋放內(nèi)存(free)前需要調(diào)用pthread_mutex_destroy.
原型:
int pthread_mutex_init(pthread_mutex_t *restrict mutex, const pthread_mutexattr_t *restric attr);
int pthread_mutex_destroy(pthread_mutex_t *mutex);
頭文件:
返回值: 成功則返回0, 出錯(cuò)則返回錯(cuò)誤編號(hào).
說(shuō)明: 如果使用默認(rèn)的屬性初始化互斥量, 只需把a(bǔ)ttr設(shè)為NULL. 其他值在以后講解.
首先說(shuō)一下加鎖函數(shù):
頭文件:
int pthread_mutex_lock(pthread_mutex_t *mutex);
int pthread_mutex_trylock(pthread_mutex_t *mutex);
返回值: 成功則返回0, 出錯(cuò)則返回錯(cuò)誤編號(hào).
說(shuō) 明: 具體說(shuō)一下trylock函數(shù), 這個(gè)函數(shù)是非阻塞調(diào)用模式, 也就是說(shuō), 如果互斥量沒(méi)被鎖住, trylock函數(shù)將把互斥量加鎖, 并獲得對(duì)共享資源的訪問(wèn)權(quán)限; 如果互斥量
被鎖住了, trylock函數(shù)將不會(huì)阻塞等待而直接返回EBUSY, 表示共享資源處于忙狀態(tài).
再說(shuō)一下解所函數(shù):
頭文件:
原型: int pthread_mutex_unlock(pthread_mutex_t *mutex);
返回值: 成功則返回0, 出錯(cuò)則返回錯(cuò)誤編號(hào).
條件變量常與互斥鎖同時(shí)使用,達(dá)到線程同步的目的:條件變量通過(guò)允許線程阻塞和等待另一個(gè)線程發(fā)送信號(hào)的方法彌補(bǔ)了互斥鎖的不足。在發(fā) 送信號(hào)時(shí),如果沒(méi)有線程
等待在該條件變量上,那么信號(hào)將丟失;而信號(hào)量有計(jì)數(shù)值,每次信號(hào)量post操作都會(huì)被記錄
1. 互斥鎖必須是誰(shuí)上鎖就由誰(shuí)來(lái)解鎖,而信號(hào)量的wait和post操作不必由同一個(gè)線程執(zhí)行。
2. 互斥鎖要么被鎖住,要么被解開(kāi),和二值信號(hào)量類(lèi)似
3. sem_post是各種同步技巧中,唯一一個(gè)能在信號(hào)處理程序中安全調(diào)用的函數(shù)
4. 互斥鎖是為上鎖而優(yōu)化的;條件變量是為等待而優(yōu)化的; 信號(hào)量既可用于上鎖,也可用于等待,因此會(huì)有更多的開(kāi)銷(xiāo)和更高的復(fù)雜性
5. 互斥鎖,條件變量都只用于同一個(gè)進(jìn)程的各線程間,而信號(hào)量(有名信號(hào)量)可用于不同進(jìn)程間的同步。當(dāng)信號(hào)量用于進(jìn)程間同步時(shí),要求信號(hào)量建立在共享內(nèi)存區(qū)。
6. 信號(hào)量有計(jì)數(shù)值,每次信號(hào)量post操作都會(huì)被記錄,而條件變量在發(fā)送信號(hào)時(shí),如果沒(méi)有線程在等待該條件變量,那么信號(hào)將丟失。
讀寫(xiě)鎖
讀寫(xiě)鎖與互斥量類(lèi)似,不過(guò)讀寫(xiě)鎖允許更高的并行性。互斥量要么是鎖住狀態(tài)要么是不加鎖狀態(tài),而且一次只有一個(gè)線程可以對(duì)其加鎖。
讀寫(xiě)鎖可以由三種狀態(tài):讀模式下加鎖狀態(tài)、寫(xiě)模式下加鎖狀態(tài)、不加鎖狀態(tài)。一次只有一個(gè)線程可以占有寫(xiě)模式的讀寫(xiě)鎖,但是多個(gè)線程可以同時(shí)占有讀模式的讀寫(xiě)
鎖。
在讀寫(xiě)鎖是寫(xiě)加鎖狀態(tài)時(shí),在這個(gè)鎖被解鎖之前,所有試圖對(duì)這個(gè)鎖加鎖的線程都會(huì)被阻塞。當(dāng)讀寫(xiě)鎖在讀加鎖狀態(tài)時(shí),所有試圖以讀模式對(duì)它進(jìn)行加鎖的線程都可以
得到訪問(wèn)權(quán),但是如果線程希望以寫(xiě)模式對(duì)此鎖進(jìn)行加鎖,它必須阻塞直到所有的線程釋放讀鎖。雖然讀寫(xiě)鎖的實(shí)現(xiàn)各不相同,但當(dāng)讀寫(xiě)鎖處于讀模式鎖住狀態(tài)時(shí),如果有
另外的線程試圖以寫(xiě)模式加鎖,讀寫(xiě)鎖通常會(huì)阻塞隨后的讀模式鎖請(qǐng)求。這樣可以避免讀模式鎖長(zhǎng)期占用,而等待的寫(xiě)模式鎖請(qǐng)求一直得不到滿足。
讀寫(xiě)鎖非常適合于對(duì)數(shù)據(jù)結(jié)構(gòu)讀的次數(shù)遠(yuǎn)大于寫(xiě)的情況。當(dāng)讀寫(xiě)鎖在寫(xiě)模式下時(shí),它所保護(hù)的數(shù)據(jù)結(jié)構(gòu)就可以被安全地修改,因?yàn)楫?dāng)前只有一個(gè)線程可以在寫(xiě)模式下?lián)?br />有這個(gè)鎖。當(dāng)讀寫(xiě)鎖在讀狀態(tài)下時(shí),只要線程獲取了讀模式下的讀寫(xiě)鎖,該鎖所保護(hù)的數(shù)據(jù)結(jié)構(gòu)可以被多個(gè)獲得讀模式鎖的線程讀取。
讀寫(xiě)鎖也叫做共享-獨(dú)占鎖,當(dāng)讀寫(xiě)鎖以讀模式鎖住時(shí),它是以共享模式鎖住的;當(dāng)他以寫(xiě)模式鎖住時(shí),它是以獨(dú)占模式鎖住的。
初始化和銷(xiāo)毀:
#include
int pthread_rwlock_init(pthread_rwlock_t *restrict rwlock, const pthread_rwlockattr_t *restrict attr);
int pthread_rwlock_destroy(pthread_rwlock_t *rwlock);
成功則返回0, 出錯(cuò)則返回錯(cuò)誤編號(hào).
同互斥量以上, 在釋放讀寫(xiě)鎖占用的內(nèi)存之前, 需要先通過(guò)pthread_rwlock_destroy對(duì)讀寫(xiě)鎖進(jìn)行清理工作, 釋放由init分配的資源.
讀和寫(xiě):
#include
int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock);
int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock);
int pthread_rwlock_unlock(pthread_rwlock_t *rwlock);
成功則返回0, 出錯(cuò)則返回錯(cuò)誤編號(hào).
這3個(gè)函數(shù)分別實(shí)現(xiàn)獲取讀鎖, 獲取寫(xiě)鎖和釋放鎖的操作. 獲取鎖的兩個(gè)函數(shù)是阻塞操作,
同樣, 非阻塞的函數(shù)為:
#include
int pthread_rwlock_tryrdlock(pthread_rwlock_t *rwlock);
int pthread_rwlock_trywrlock(pthread_rwlock_t *rwlock);
成功則返回0, 出錯(cuò)則返回錯(cuò)誤編號(hào).
非阻塞的獲取鎖操作, 如果可以獲取則返回0, 否則返回錯(cuò)誤的EBUSY.
雖然讀寫(xiě)鎖提高了并行性,但是就速度而言并不比互斥量快.
可能這也是即使有讀寫(xiě)鎖存在還會(huì)使用互斥量的原因,因?yàn)樗谒俣确矫媛詣僖换I。這就需要我們?cè)趯?xiě)程序的時(shí)候
綜合考慮速度和并行性并找到一個(gè)折中。
比如: 假設(shè)使用互斥量需要0.5秒,使用讀寫(xiě)鎖需要0.8秒。在類(lèi)似學(xué)生管理系統(tǒng)這類(lèi)軟件中,可能百分之九十的
時(shí)間都是查詢操作,那么假如現(xiàn)在突然來(lái)個(gè)個(gè)20個(gè)請(qǐng)求,如果使用的是互斥量,那么最后的那個(gè)查詢請(qǐng)求被滿足需要10后。這樣,估計(jì)沒(méi)人能受得了。而使用讀寫(xiě)鎖,應(yīng)為
讀鎖能夠多次獲得。所以所有的20個(gè)請(qǐng)求,每個(gè)請(qǐng)求都能在1秒左右
得到滿足。
也就是說(shuō),在一些寫(xiě)操作比較多或是本身需要同步的地方并不多的程序中我們應(yīng)該使用互斥量,而在讀操作遠(yuǎn)大于寫(xiě)操作的一些程序中我們應(yīng)該使用讀寫(xiě)鎖來(lái)進(jìn)行同步
條件變量(condition)
條件變量與互斥量一起使用時(shí),允許線程以無(wú)競(jìng)爭(zhēng)的方式等待特定的條件發(fā)生。
條件本身是由互斥量保護(hù)的。線程在改變條件狀態(tài)前必須首先鎖住互斥量,其它線程在獲得互斥量之前不會(huì)察覺(jué)到這種改變,因此必須鎖定互斥量以后才能計(jì)算條件。
條件的檢測(cè)是在互斥鎖的保護(hù)下進(jìn)行的。如果一個(gè)條件為假,一個(gè)線程自動(dòng)阻塞,并釋放等待狀態(tài)改變的互斥鎖。如果另一個(gè)線程改變了條件,它發(fā)信號(hào)給關(guān)聯(lián)的條件
變量,喚醒一個(gè)或多個(gè)等待它的線程,重新獲得互斥鎖,重新評(píng)價(jià)條件。如果兩進(jìn)程共享可讀寫(xiě)的內(nèi)存,條件變量可以被用來(lái)實(shí)現(xiàn)這兩進(jìn)程間的線程同步。
1. 初始化:
條件變量采用的數(shù)據(jù)類(lèi)型是pthread_cond_t, 在使用之前必須要進(jìn)行初始化, 這包括兩種方式:
靜態(tài): 可以把常量PTHREAD_COND_INITIALIZER給靜態(tài)分配的條件變量.
動(dòng)態(tài): pthread_cond_init函數(shù), 是釋放動(dòng)態(tài)條件變量的內(nèi)存空間之前, 要用pthread_cond_destroy對(duì)其進(jìn)行清理.
#include
int pthread_cond_init(pthread_cond_t *restrict cond, pthread_condattr_t *restrict attr);
int pthread_cond_destroy(pthread_cond_t *cond);
成功則返回0, 出錯(cuò)則返回錯(cuò)誤編號(hào).
注意:條件變量占用的空間并未被釋放。
當(dāng)pthread_cond_init的attr參數(shù)為NULL時(shí), 會(huì)創(chuàng)建一個(gè)默認(rèn)屬性的條件變量; 非默認(rèn)情況以后討論.
2. 等待條件:
#include
int pthread_cond_wait(pthread_cond_t *restrict cond, pthread_mutex_t *restric mutex);
int pthread_cond_timedwait(pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex, const struct timespec *restrict timeout);
成功則返回0, 出錯(cuò)則返回錯(cuò)誤編號(hào).
這兩個(gè)函數(shù)分別是阻塞等待和超時(shí)等待.
等待條件函數(shù)等待條件變?yōu)檎? 傳遞給pthread_cond_wait的互斥量對(duì)條件進(jìn)行保護(hù), 調(diào)用者把鎖住的互斥量傳遞給函數(shù). 函數(shù)把調(diào)用線程放到等待條件的線程列表上,
然后對(duì)互斥量解鎖, 這兩個(gè)操作是原子的. 這樣 便關(guān)閉了條件檢查和線程進(jìn)入休眠狀態(tài)等待條件改變這兩個(gè)操作之間的時(shí)間通道, 這樣線程就不會(huì)錯(cuò)過(guò)條件的任何變化.
當(dāng)pthread_cond_wait返回時(shí), 互斥量再次被鎖住.
pthread_cond_wait函數(shù)的返回并不意味著條件的值一定發(fā)生了變化,必須重新檢查條件的值。
pthread_cond_wait函數(shù)返回時(shí),相應(yīng)的互斥鎖將被當(dāng)前線程鎖定,即使是函數(shù)出錯(cuò)返回。
阻塞在條件變量上的線程被喚醒以后,直到pthread_cond_wait()函數(shù)返回之前條件的值都有可能發(fā)生變化。所以函數(shù)返回以后,在鎖定相應(yīng)的互斥鎖之前,必須重新測(cè)試條
件值。最好的測(cè)試方法是循環(huán)調(diào)用pthread_cond_wait函數(shù),并把滿足條件的表達(dá)式置為循環(huán)的終止條件。如:
pthread_mutex_lock();
while (condition_is_false)
pthread_cond_wait();
pthread_mutex_unlock();
阻塞在同一個(gè)條件變量上的不同線程被釋放的次序是不一定的。
注意:pthread_cond_wait()函數(shù)是退出點(diǎn),如果在調(diào)用這個(gè)函數(shù)時(shí),已有一個(gè)掛起的退出請(qǐng)求,且線程允許退出,這個(gè)線程將被終止并開(kāi)始執(zhí)行善后處理函數(shù),而這時(shí)和條
件變量相關(guān)的互斥鎖仍將處在鎖定狀態(tài)。
pthread_cond_timedwait函數(shù)到了一定的時(shí)間,即使條件未發(fā)生也會(huì)解除阻塞。這個(gè)時(shí)間由參數(shù)abstime指定。函數(shù)返回時(shí),相應(yīng)的互斥鎖往往是鎖定的,即使是函數(shù)出錯(cuò)返
回。
注意:pthread_cond_timedwait函數(shù)也是退出點(diǎn)。
超時(shí)時(shí)間參數(shù)是指一天中的某個(gè)時(shí)刻。使用舉例:
pthread_timestruc_t to;
to.tv_sec = time(NULL) + TIMEOUT;
to.tv_nsec = 0;
超時(shí)返回的錯(cuò)誤碼是ETIMEDOUT。
3. 通知條件:
#include
int pthread_cond_signal(pthread_cond_t *cond);
int pthread_cond_broadcast(pthread_cond_t *cond);
成功則返回0, 出錯(cuò)則返回錯(cuò)誤編號(hào).
這兩個(gè)函數(shù)用于通知線程條件已經(jīng)滿足. 調(diào)用這兩個(gè)函數(shù), 也稱(chēng)向線程或條件發(fā)送信號(hào). 必須注意, 一定要在改變條件狀態(tài)以后再給線程發(fā)送信號(hào).
|
新聞熱點(diǎn)
疑難解答
圖片精選