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