同步和互斥
互斥:多線程中互斥是指多個(gè)線程訪問(wèn)同一資源時(shí)同時(shí)只允許一個(gè)線程對(duì)其進(jìn)行訪問(wèn),具有唯一性和排它性。但互斥無(wú)法限制訪問(wèn)者對(duì)資源的訪問(wèn)順序,即訪問(wèn)是無(wú)序的;
同步:多線程同步是指在互斥的基礎(chǔ)上(大多數(shù)情況),通過(guò)其它機(jī)制實(shí)現(xiàn)訪問(wèn)者對(duì)資源的有序訪問(wèn)。在大多數(shù)情況下,同步已經(jīng)實(shí)現(xiàn)了互斥,特別是所有寫(xiě)入資源的情況必定是互斥的。少數(shù)情況是指可以允許多個(gè)訪問(wèn)者同時(shí)訪問(wèn)資源。
互斥鎖
在多任務(wù)操作系統(tǒng)中,同時(shí)運(yùn)行的多個(gè)任務(wù)可能都需要使用同一種資源。為了同一時(shí)刻只允許一個(gè)任務(wù)訪問(wèn)資源,需要用互斥鎖對(duì)資源進(jìn)行保護(hù)。互斥鎖是一種簡(jiǎn)單的加鎖的方法來(lái)控制對(duì)共享資源的訪問(wèn),互斥鎖只有兩種狀態(tài),即上鎖( lock )和解鎖( unlock )。
互斥鎖操作基本流程
訪問(wèn)共享資源前,對(duì)互斥鎖進(jìn)行加鎖
完成加鎖后訪問(wèn)共享資源
對(duì)共享資源完成訪問(wèn)后,對(duì)互斥鎖進(jìn)行解鎖
對(duì)互斥鎖進(jìn)行加鎖后,任何其他試圖再次對(duì)互斥鎖加鎖的線程將會(huì)被阻塞,直到鎖被釋放
互斥鎖特性
原子性:互斥鎖是一個(gè)原子操作,操作系統(tǒng)保證如果一個(gè)線程鎖定了一個(gè)互斥鎖,那么其他線程在同一時(shí)間不會(huì)成功鎖定這個(gè)互斥鎖
唯一性:如果一個(gè)線程鎖定了一個(gè)互斥鎖,在它解除鎖之前,其他線程不可以鎖定這個(gè)互斥鎖
非忙等待:如果一個(gè)線程已經(jīng)鎖定了一個(gè)互斥鎖,第二個(gè)線程又試圖去鎖定這個(gè)互斥鎖,則第二個(gè)線程將被掛起且不占用任何CPU資源,直到第一個(gè)線程解除對(duì)這個(gè)互斥鎖的鎖定為止,第二個(gè)線程則被喚醒并繼續(xù)執(zhí)行,同時(shí)鎖定這個(gè)互斥鎖
示例
#include #include #include #include #include char*pTestBuf=nullptr;//全局變量 /*定義互斥鎖*/ pthread_mutex_tmutex; void*ThrTestMutex(void*p) { pthread_mutex_lock(&mutex);//加鎖 { pTestBuf=(char*)p; sleep(1); } pthread_mutex_unlock(&mutex);//解鎖 } intmain() { /*初始化互斥量,默認(rèn)屬性*/ pthread_mutex_init(&mutex,NULL); /*創(chuàng)建兩個(gè)線程對(duì)共享資源訪問(wèn)*/ pthread_ttid1,tid2; pthread_create(&tid1,NULL,ThrTestMutex,(void*)"Thread1"); pthread_create(&tid2,NULL,ThrTestMutex,(void*)"Thread2"); /*等待線程結(jié)束*/ pthread_join(tid1,NULL); pthread_join(tid2,NULL); /*銷毀互斥鎖*/ pthread_mutex_destroy(&mutex); return0; }
讀寫(xiě)鎖
讀寫(xiě)鎖允許更高的并行性,也叫共享互斥鎖。互斥量要么是加鎖狀態(tài),要么就是解鎖狀態(tài),而且一次只有一個(gè)線程可以對(duì)其加鎖。讀寫(xiě)鎖可以有3種狀態(tài):讀模式下加鎖狀態(tài)、寫(xiě)模式加鎖狀態(tài)、不加鎖狀態(tài)。一次只有一個(gè)線程可以占有寫(xiě)模式的讀寫(xiě)鎖,但是多個(gè)線程可以同時(shí)占有讀模式的讀寫(xiě)鎖,即允許多個(gè)線程讀但只允許一個(gè)線程寫(xiě)。
當(dāng)讀操作較多,寫(xiě)操作較少時(shí),可用讀寫(xiě)鎖提高線程讀并發(fā)性
讀寫(xiě)鎖特性
如果有線程讀數(shù)據(jù),則允許其它線程執(zhí)行讀操作,但不允許寫(xiě)操作
如果有線程寫(xiě)數(shù)據(jù),則其它線程都不允許讀、寫(xiě)操作
如果某線程申請(qǐng)了讀鎖,其它線程可以再申請(qǐng)讀鎖,但不能申請(qǐng)寫(xiě)鎖
如果某線程申請(qǐng)了寫(xiě)鎖,其它線程不能申請(qǐng)讀鎖,也不能申請(qǐng)寫(xiě)鎖
讀寫(xiě)鎖適合于對(duì)數(shù)據(jù)的讀次數(shù)比寫(xiě)次數(shù)多得多的情況
讀寫(xiě)鎖創(chuàng)建和銷毀
#include intphtread_rwlock_init(pthread_rwlock_t*restrictrwlock,constpthread_rwlockattr_t*restrictattr); intpthread_rwlock_destroy(pthread_rwlock_t*rwlock);
參數(shù):rwlock:讀寫(xiě)鎖,attr:讀寫(xiě)鎖屬性
返回值:成功返回0,出錯(cuò)返回錯(cuò)誤碼
讀寫(xiě)鎖加鎖解鎖
#include /**加讀鎖*/ intpthread_rwlock_rdlock(pthread_rwlock_t*rwlock); /**加寫(xiě)鎖*/ intpthread_rwlock_wrlock(pthread_rwlock_t*rwlock); /**釋放鎖*/ intpthread_rwlock_unlock(pthread_rwlock_t*rwlock);
參數(shù):rwlock:讀寫(xiě)鎖
返回值:成功返回 0;出錯(cuò),返回錯(cuò)誤碼
示例
#include #include #include #include #include /*定義讀寫(xiě)鎖*/ pthread_rwlock_trwlock; /*定義共享資源變量*/ intg_nNum=0; /*讀操作其他線程允許讀操作不允許寫(xiě)操作*/ void*fun1(void*arg) { while(1) { pthread_rwlock_rdlock(&rwlock); { printf("readthread1==%d ",g_nNum); } pthread_rwlock_unlock(&rwlock); sleep(1); } } /*讀操作,其他線程允許讀操作,不允許寫(xiě)操作*/ void*fun2(void*arg) { while(1) { pthread_rwlock_rdlock(&rwlock); { printf("readthread2==%d ",g_nNum); } pthread_rwlock_unlock(&rwlock); sleep(1); } } /*寫(xiě)操作,其它線程都不允許讀或?qū)懖僮?/ void*fun3(void*arg) { while(1) { pthread_rwlock_wrlock(&rwlock); { g_nNum++; printf("writethread1 "); } pthread_rwlock_unlock(&rwlock); sleep(1); } } /*寫(xiě)操作,其它線程都不允許讀或?qū)懖僮?/ void*fun4(void*arg) { while(1) { pthread_rwlock_wrlock(&rwlock); { g_nNum++; printf("writethread2 "); } pthread_rwlock_unlock(&rwlock); sleep(1); } } intmain(intarc,char*argv[]) { pthread_tThrId1,ThrId2,ThrId3,ThrId4; pthread_rwlock_init(&rwlock,NULL);//初始化一個(gè)讀寫(xiě)鎖 /*創(chuàng)建測(cè)試線程*/ pthread_create(&ThrId1,NULL,fun1,NULL); pthread_create(&ThrId2,NULL,fun2,NULL); pthread_create(&ThrId3,NULL,fun3,NULL); pthread_create(&ThrId4,NULL,fun4,NULL); /*等待線程結(jié)束,回收其資源*/ pthread_join(ThrId1,NULL); pthread_join(ThrId2,NULL); pthread_join(ThrId3,NULL); pthread_join(ThrId4,NULL); pthread_rwlock_destroy(&rwlock);//銷毀讀寫(xiě)鎖 return0; }
結(jié)果
自旋鎖
自旋鎖與互斥鎖功能相同,唯一不同的就是互斥鎖阻塞后休眠不占用CPU,而自旋鎖阻塞后不會(huì)讓出CPU,會(huì)一直忙等待,直到得到鎖
自旋鎖在用戶態(tài)較少用,而在內(nèi)核態(tài)使用的比較多
自旋鎖的使用場(chǎng)景:鎖的持有時(shí)間比較短,或者說(shuō)小于2次上下文切換的時(shí)間
自旋鎖在用戶態(tài)的函數(shù)接口和互斥量一樣,把pthread_mutex_lock()/pthread_mutex_unlock()中mutex換成spin,如:pthread_spin_init()
自旋鎖函數(shù)
linux中的自旋鎖用結(jié)構(gòu)體spinlock_t 表示,定義在include/linux/spinlock_type.h。自旋鎖的接口函數(shù)全部定義在include/linux/spinlock.h頭文件中,實(shí)際使用時(shí)只需include即可
示例
include spinlock_tlock;//定義自旋鎖 spin_lock_init(&lock);//初始化自旋鎖 spin_lock(&lock);//獲得鎖,如果沒(méi)獲得成功則一直等待 { .......//處理臨界資源 } spin_unlock(&lock);//釋放自旋鎖
條件變量
條件變量用來(lái)阻塞一個(gè)線程,直到條件發(fā)生。通常條件變量和互斥鎖同時(shí)使用。條件變量使線程可以睡眠等待某種條件滿足。條件變量是利用線程間共享的全局變量進(jìn)行同步的一種機(jī)制。
條件變量的邏輯:一個(gè)線程掛起去等待條件變量的條件成立,而另一個(gè)線程使條件成立。
基本原理
線程在改變條件狀態(tài)之前先鎖住互斥量。如果條件為假,線程自動(dòng)阻塞,并釋放等待狀態(tài)改變的互斥鎖。如果另一個(gè)線程改變了條件,它發(fā)信號(hào)給關(guān)聯(lián)的條件變量,喚醒一個(gè)或多個(gè)等待它的線程。如果兩進(jìn)程共享可讀寫(xiě)的內(nèi)存,條件變量可以被用來(lái)實(shí)現(xiàn)這兩進(jìn)程間的線程同步
示例
#include #include #include #include pthread_cond_ttaxicond=PTHREAD_COND_INITIALIZER; pthread_mutex_ttaximutex=PTHREAD_MUTEX_INITIALIZER; void*ThrFun1(void*name) { char*p=(char*)name; //加鎖,把信號(hào)量加入隊(duì)列,釋放信號(hào)量 pthread_mutex_lock(&taximutex); { pthread_cond_wait(&taxicond,&taximutex); } pthread_mutex_unlock(&taximutex); printf("ThrFun1:%snowgotasignal! ",p); pthread_exit(NULL); } void*ThrFun2(void*name) { char*p=(char*)name; printf("ThrFun2:%scondsignal. ",p);//發(fā)信號(hào) pthread_cond_signal(&taxicond); pthread_exit(NULL); } intmain(intargc,char**argv) { pthread_tThread1,Thread2; pthread_attr_tthreadattr; pthread_attr_init(&threadattr);//線程屬性初始化 //創(chuàng)建三個(gè)線程 pthread_create(&Thread1,&threadattr,ThrFun1,(void*)"Thread1"); sleep(1); pthread_create(&Thread2,&threadattr,ThrFun2,(void*)"Thread2"); sleep(1); pthread_join(Thread1,NULL); pthread_join(Thread2,NULL); return0; }
結(jié)果
虛假喚醒
當(dāng)線程從等待已發(fā)出信號(hào)的條件變量中醒來(lái),卻發(fā)現(xiàn)它等待的條件不滿足時(shí),就會(huì)發(fā)生虛假喚醒。之所以稱為虛假,是因?yàn)樵摼€程似乎無(wú)緣無(wú)故地被喚醒了。但是虛假喚醒不會(huì)無(wú)緣無(wú)故發(fā)生:它們通常是因?yàn)樵诎l(fā)出條件變量信號(hào)和等待線程最終運(yùn)行之間,另一個(gè)線程運(yùn)行并更改了條件
避免虛假喚醒
在wait端,我們必須把判斷條件和wait()放到while循環(huán)中
pthread_mutex_lock(&taximutex); { while(value!=wantValue) { pthread_cond_wait(&taxicond,&taximutex); } } pthread_mutex_unlock(&taximutex);
信號(hào)量
信號(hào)量用于進(jìn)程或線程間的同步和互斥,信號(hào)量本質(zhì)上是一個(gè)非負(fù)的整數(shù)計(jì)數(shù)器,它被用來(lái)控制對(duì)公共資源的訪問(wèn)。編程時(shí)可根據(jù)操作信號(hào)量值的結(jié)果判斷是否對(duì)公共資源具有訪問(wèn)的權(quán)限,當(dāng)信號(hào)量值大于0時(shí),則可以訪問(wèn),否則將阻塞
#include //初始化信號(hào)量 intsem_init(sem_t*sem,intpshared,unsignedintvalue); //信號(hào)量P操作(減1) intsem_wait(sem_t*sem); //以非阻塞的方式來(lái)對(duì)信號(hào)量進(jìn)行減1操作 intsem_trywait(sem_t*sem); //信號(hào)量V操作(加1) intsem_post(sem_t*sem); //獲取信號(hào)量的值 intsem_getvalue(sem_t*sem,int*sval); //銷毀信號(hào)量 intsem_destroy(sem_t*sem);
示例
//信號(hào)量用于同步實(shí)例 #include #include #include #include sem_tsem_g,sem_p;//定義兩個(gè)信號(hào)量 chars8Test='a'; void*pthread_g(void*arg)//此線程改變字符的值 { while(1) { sem_wait(&sem_g); s8Test++; sleep(2); sem_post(&sem_p); } } void*pthread_p(void*arg)//此線程打印字符的值 { while(1) { sem_wait(&sem_p); printf("%c",s8Test); fflush(stdout); sem_post(&sem_g); } } intmain(intargc,char*argv[]) { pthread_ttid1,tid2; sem_init(&sem_g,0,0);//初始化信號(hào)量為0 sem_init(&sem_p,0,1);//初始化信號(hào)量為1 pthread_create(&tid1,NULL,pthread_g,NULL); pthread_create(&tid2,NULL,pthread_p,NULL); pthread_join(tid1,NULL); pthread_join(tid2,NULL); return0; }
結(jié)果
審核編輯:劉清
-
cpu
+關(guān)注
關(guān)注
68文章
10825瀏覽量
211146 -
Linux
+關(guān)注
關(guān)注
87文章
11227瀏覽量
208922 -
計(jì)數(shù)器
+關(guān)注
關(guān)注
32文章
2253瀏覽量
94352 -
信號(hào)量
+關(guān)注
關(guān)注
0文章
53瀏覽量
8314 -
Linux編程
+關(guān)注
關(guān)注
0文章
5瀏覽量
614
原文標(biāo)題:詳解Linux多線程中互斥鎖、讀寫(xiě)鎖、自旋鎖、條件變量、信號(hào)量
文章出處:【微信號(hào):良許Linux,微信公眾號(hào):良許Linux】歡迎添加關(guān)注!文章轉(zhuǎn)載請(qǐng)注明出處。
發(fā)布評(píng)論請(qǐng)先 登錄
相關(guān)推薦
評(píng)論