Hello、Hello大家好,我是木榮,今天我們繼續(xù)來(lái)聊一聊Linux中多線程編程中的重要知識(shí)點(diǎn),詳細(xì)談?wù)劧嗑€程中同步和互斥機(jī)制。
同步和互斥
- 互斥:多線程中互斥是指多個(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ù)?;コ怄i是一種簡(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_t mutex;
void *ThrTestMutex(void *p)
{
pthread_mutex_lock(&mutex); // 加鎖
{
pTestBuf = (char*)p;
sleep(1);
}
pthread_mutex_unlock(&mutex); // 解鎖
}
int main()
{
/* 初始化互斥量, 默認(rèn)屬性 */
pthread_mutex_init(&mutex, NULL);
/* 創(chuàng)建兩個(gè)線程對(duì)共享資源訪問(wèn) */
pthread_t tid1, tid2;
pthread_create(&tid1, NULL, ThrTestMutex, (void *)"Thread1");
pthread_create(&tid2, NULL, ThrTestMutex, (void *)"Thread2");
/* 等待線程結(jié)束 */
pthread_join(tid1, NULL);
pthread_join(tid2, NULL);
/* 銷(xiāo)毀互斥鎖 */
pthread_mutex_destroy(&mutex);
return 0;
}
讀寫(xiě)鎖
- 讀寫(xiě)鎖允許更高的并行性,也叫共享互斥鎖?;コ饬恳词羌渔i狀態(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)建和銷(xiāo)毀
#include
int phtread_rwlock_init(pthread_rwlock_t *restrict rwlock, const pthread_rwlockattr_t *restrict attr);
int pthread_rwlock_destroy(pthread_rwlock_t *rwlock);
- 參數(shù):rwlock:讀寫(xiě)鎖,attr:讀寫(xiě)鎖屬性
- 返回值:成功返回0,出錯(cuò)返回錯(cuò)誤碼
讀寫(xiě)鎖加鎖解鎖
#include
/** 加讀鎖 */
int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock);
/** 加寫(xiě)鎖 */
int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock);
/** 釋放鎖 */
int pthread_rwlock_unlock(pthread_rwlock_t *rwlock);
- 參數(shù):rwlock:讀寫(xiě)鎖
- 返回值:成功返回 0;出錯(cuò),返回錯(cuò)誤碼
示例
#include
#include
#include
#include
#include
/* 定義讀寫(xiě)鎖 */
pthread_rwlock_t rwlock;
/* 定義共享資源變量 */
int g_nNum = 0;
/* 讀操作 其他線程允許讀操作 不允許寫(xiě)操作 */
void *fun1(void *arg)
{
while(1)
{
pthread_rwlock_rdlock(&rwlock);
{
printf("read thread 1 == %d\\n", g_nNum);
}
pthread_rwlock_unlock(&rwlock);
sleep(1);
}
}
/* 讀操作,其他線程允許讀操作,不允許寫(xiě)操作 */
void *fun2(void *arg)
{
while(1)
{
pthread_rwlock_rdlock(&rwlock);
{
printf("read thread 2 == %d\\n", g_nNum);
}
pthread_rwlock_unlock(&rwlock);
sleep(1);
}
}
/* 寫(xiě)操作,其它線程都不允許讀或?qū)懖僮?*/
void *fun3(void *arg)
{
while(1)
{
pthread_rwlock_wrlock(&rwlock);
{
g_nNum++;
printf("write thread 1\\n");
}
pthread_rwlock_unlock(&rwlock);
sleep(1);
}
}
/* 寫(xiě)操作,其它線程都不允許讀或?qū)懖僮?*/
void *fun4(void *arg)
{
while(1)
{
pthread_rwlock_wrlock(&rwlock);
{
g_nNum++;
printf("write thread 2\\n");
}
pthread_rwlock_unlock(&rwlock);
sleep(1);
}
}
int main(int arc, char *argv[])
{
pthread_t ThrId1, 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āo)毀讀寫(xiě)鎖
return 0;
}
結(jié)果
自旋鎖
- 自旋鎖與互斥鎖功能相同,唯一不同的就是互斥鎖阻塞后休眠不占用CPU,而自旋鎖阻塞后不會(huì)讓出CPU,會(huì)一直忙等待,直到得到鎖
- 自旋鎖在用戶(hù)態(tài)較少用,而在內(nèi)核態(tài)使用的比較多
- 自旋鎖的使用場(chǎng)景:鎖的持有時(shí)間比較短,或者說(shuō)小于2次上下文切換的時(shí)間
- 自旋鎖在用戶(hù)態(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
條件變量
- 條件變量用來(lái)阻塞一個(gè)線程,直到條件發(fā)生。通常條件變量和互斥鎖同時(shí)使用。條件變量使線程可以睡眠等待某種條件滿(mǎn)足。條件變量是利用線程間共享的全局變量進(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_t taxicond = PTHREAD_COND_INITIALIZER;
pthread_mutex_t taximutex = 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: %s now got a signal!\\n", p);
pthread_exit(NULL);
}
void *ThrFun2(void *name)
{
char *p = (char *)name;
printf ("ThrFun2: %s cond signal.\\n", p); // 發(fā)信號(hào)
pthread_cond_signal(&taxicond);
pthread_exit(NULL);
}
int main (int argc, char **argv)
{
pthread_t Thread1, Thread2;
pthread_attr_t threadattr;
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);
return 0;
}
結(jié)果
虛假喚醒
- 當(dāng)線程從等待已發(fā)出信號(hào)的條件變量中醒來(lái),卻發(fā)現(xiàn)它等待的條件不滿(mǎn)足時(shí),就會(huì)發(fā)生虛假喚醒。之所以稱(chēng)為虛假,是因?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)量
int sem_init(sem_t *sem, int pshared, unsigned int value);
// 信號(hào)量P操作(減 1)
int sem_wait(sem_t *sem);
// 以非阻塞的方式來(lái)對(duì)信號(hào)量進(jìn)行減1操作
int sem_trywait(sem_t *sem);
// 信號(hào)量V操作(加 1)
int sem_post(sem_t *sem);
// 獲取信號(hào)量的值
int sem_getvalue(sem_t *sem, int *sval);
// 銷(xiāo)毀信號(hào)量
int sem_destroy(sem_t *sem);
示例
// 信號(hào)量用于同步實(shí)例
#include
#include
#include
#include
sem_t sem_g,sem_p; //定義兩個(gè)信號(hào)量
char s8Test = '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);
}
}
int main(int argc, char *argv[])
{
pthread_t tid1,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);
return 0;
}
結(jié)果
結(jié)束語(yǔ)
好了,通過(guò)這篇文章希望對(duì)小伙伴們有所幫助,希望能更深刻的理解多線程編程中的知識(shí)。
評(píng)論
查看更多