中企網(wǎng)站建設(shè)app推廣軟件
目錄
一、概念
二、互斥鎖實(shí)現(xiàn)互斥
三、條件變量實(shí)現(xiàn)同步
????????銀行家算法
生產(chǎn)者與消費(fèi)者模型
一、概念
概念:在多線程程序中,如果涉及到了對(duì)共享資源的操作,則有可能會(huì)導(dǎo)致數(shù)據(jù)二義性,而線程安全就指的是,就算對(duì)共享資源進(jìn)行操作也不會(huì)導(dǎo)致數(shù)據(jù)二義
實(shí)現(xiàn):如何實(shí)現(xiàn)多線程中對(duì)共享資源的操作不會(huì)出問(wèn)題
??????? 互斥:通過(guò)同一時(shí)間對(duì)資源訪問(wèn)的唯一性,保證訪問(wèn)安全
?????????????? ? 互斥的實(shí)現(xiàn):互斥鎖(讀寫(xiě)鎖、自旋鎖...)
??????? 同步:通過(guò)條件控制,讓多執(zhí)行對(duì)資源的獲取更加合理
???????????????? 同步實(shí)現(xiàn):條件變量、信號(hào)量
二、互斥鎖實(shí)現(xiàn)互斥
實(shí)現(xiàn)對(duì)共享資源的唯一訪問(wèn)
1)互斥鎖實(shí)現(xiàn)互斥的原理
??????? 本質(zhì):就是一個(gè)1/0計(jì)數(shù)器,通過(guò)0/1標(biāo)記資源的訪問(wèn)狀態(tài)(0-不可訪問(wèn)、1-可訪問(wèn))
?????????????????? 在訪問(wèn)資源之前進(jìn)行加鎖(通過(guò)狀態(tài)判斷是否可訪問(wèn),不可訪問(wèn)則阻塞)
?????????????????? 在訪問(wèn)資源之后進(jìn)行解鎖(將資源狀態(tài)置為可訪問(wèn)狀態(tài),喚醒其他阻塞的線程)
????????也有另一種理解:訪問(wèn)資源之前加鎖(獲取鎖資源-獲取不到就阻塞)
???????????????????????????? ????????訪問(wèn)完畢解鎖(歸還鎖資源)
??????? 多個(gè)線程想要實(shí)現(xiàn)互斥,就必須訪問(wèn)同一個(gè)鎖才可以,也就意味著鎖也是一個(gè)共享資源
?互斥鎖的操作本身必須是安全的:互斥鎖本身計(jì)數(shù)器的操作時(shí)原子操作
2)互斥鎖如何實(shí)現(xiàn)自身操作安全的原理
?我們知道內(nèi)存與cpu之間的數(shù)據(jù)傳輸:當(dāng)進(jìn)行加鎖操作時(shí),先將鎖中的1置入cpu(先將變量數(shù)據(jù)從內(nèi)存加載到cpu)然后才能從cpu中對(duì)數(shù)據(jù)進(jìn)行處理(轉(zhuǎn)化為0)然后在從cpu中將數(shù)據(jù)加載到內(nèi)存指定位置(完成將1替換為0)
然而這樣一種操作就會(huì)產(chǎn)生問(wèn)題,如果1從內(nèi)存加載到cpu還沒(méi)有將處理后的0加載回內(nèi)存時(shí),這時(shí)切換其他線程運(yùn)行訪問(wèn)到的鎖資源仍然為1,就會(huì)繼續(xù)進(jìn)行加鎖,這無(wú)疑是致命的。
所以對(duì)于鎖資源本身來(lái)說(shuō),計(jì)數(shù)器的操作必須是原子性的才可以。
有個(gè)指令類(lèi)似于exchange,功能是交換指定寄存器與內(nèi)存中的數(shù)據(jù)
??????? 1、先將指定寄存器中的值修改為0,
??????? 2、將寄存器與內(nèi)存中的數(shù)據(jù)進(jìn)行互換
??????? 3、判斷是否符合獲取鎖的條件或者說(shuō)判斷是否能夠加鎖
?這樣使用exchange指令就可以實(shí)現(xiàn)計(jì)數(shù)器操作為原子操作。
3)接口
互斥鎖類(lèi)型變量????? pthread_mutex_t
初始化互斥鎖
??????? int pthread_mutex_init(pthread_mutex_t *mutex,? pthread_mutexattr_t *attr)
??????? ????????mutex:互斥鎖變量的地址??????? attr:互斥鎖變量屬性(通常置NULL)
訪問(wèn)資源前加鎖
??????? int pthread_mutex_lock(pthread_mutex_t *mutex)??????? 阻塞加鎖(老實(shí)人)
??????? int pthread_mutex_trylock(pthread_mutex_t *mutex)??????? 非阻塞加鎖(海王)
訪問(wèn)資源后解鎖
??????? int pthread_mutex_unlock(pthread_mutex_t *mutex)
釋放銷(xiāo)毀互斥鎖
??????? int pthread_mutex_destroy(pthread_mutex_t *mutex)
??????? pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER (這種初始化不需要銷(xiāo)毀)
4)代碼模擬
當(dāng)不使用互斥鎖線程之間對(duì)同一變量的訪問(wèn)情況如何?
#include<stdio.h>
#include<pthread.h>
#include<unistd.h>int ticket = 100;
void *Scalper(void *arg)
{while(1){if(ticket > 0){usleep(10);printf("%p:我搶到了第 %d 號(hào)票\n",pthread_self(), ticket);ticket--;}else{printf("%p:票完了,我的工作結(jié)束了\n", pthread_self());break;}}return NULL;
}
int main()
{pthread_t tid[4];for(int i = 0; i < 4; i++){int ret = pthread_create(&tid[i], NULL, Scalper, NULL);if(ret != 0){perror("create error");return -1;}}for(int i = 0; i < 4; i++){pthread_join(tid[i], NULL);}return 0;
}
發(fā)現(xiàn)出現(xiàn)了很多意外情況,重復(fù)、負(fù)數(shù)票號(hào),
為什么呢?就是因?yàn)槊總€(gè)線程之間訪問(wèn)同一變量,一個(gè)線程還正對(duì)變量進(jìn)行操作的時(shí)候另一個(gè)線程也進(jìn)行操作,于是就發(fā)生了同一變量的多次出現(xiàn)。
?使用互斥鎖保護(hù)臨界區(qū)
#include<stdio.h>
#include<pthread.h>
#include<unistd.h>int ticket = 100;
void *Scalper(void *arg)
{while(1){pthread_mutex_lock(arg); // 訪問(wèn)之前加鎖if(ticket > 0){usleep(10);printf("%p:我搶到了第 %d 號(hào)票\n",pthread_self(), ticket);ticket--;pthread_mutex_unlock(arg); // 在所有有可能退出的地方解鎖}else{printf("%p:票完了,我的工作結(jié)束了\n", pthread_self());pthread_mutex_unlock(arg); // 在所有有可能退出的地方解鎖break;}usleep(1);}return NULL;
}
int main()
{pthread_mutex_t mutex; // 定義鎖變量 mutexpthread_mutex_init(&mutex, NULL); // 初始化鎖資源pthread_t tid[4];for(int i = 0; i < 4; i++){int ret = pthread_create(&tid[i], NULL, Scalper, &mutex); // 注意鎖資源通過(guò)線程創(chuàng)建第四個(gè)參數(shù)傳入入口函數(shù)內(nèi)if(ret != 0){perror("create error");return -1;}}for(int i = 0; i < 4; i++){pthread_join(tid[i], NULL);}return 0;
}
5)死鎖
死鎖是一種狀態(tài),是一種因?yàn)橘Y源爭(zhēng)搶不當(dāng)導(dǎo)致程序流程卡死無(wú)法繼續(xù)向下推進(jìn)的狀態(tài)。
多個(gè)線程對(duì)鎖資源的爭(zhēng)搶使用不當(dāng)導(dǎo)致程序流程卡死,無(wú)法繼續(xù)向下推進(jìn)的狀態(tài)
1、加鎖之后沒(méi)有釋放就退出,導(dǎo)致其他線程獲取不到鎖資源卡死
2、多鎖使用時(shí),加鎖順序不當(dāng),線程1加鎖順序?yàn)锳B,線程2加鎖順序?yàn)锽A
發(fā)生死鎖的必要條件
??????? ① 互斥條件??????? 同一時(shí)間一把鎖只能被一個(gè)線程所獲取到
??????? ② 不可剝奪條件??????? 一個(gè)線程加的鎖,只能自己釋放,其他線程無(wú)法釋放
??????? ③ 請(qǐng)求與保持??????? 一個(gè)線程請(qǐng)求了A鎖之后請(qǐng)求B鎖,如果請(qǐng)求不到就不會(huì)釋放A鎖
??????? ④ 環(huán)路等待??????? 線程1加了A鎖后請(qǐng)求B鎖,線程2加了B鎖后請(qǐng)求A鎖
死鎖的預(yù)防:破壞死鎖產(chǎn)生的必要條件
??????? ① 和 ② 無(wú)法修改
??????? 寫(xiě)代碼時(shí)要注意:
??????? 1、線程之間的加解鎖順序盡量一致 -- 盡可能預(yù)防環(huán)路等待
??????? 2、采用非阻塞加鎖,如果加不上鎖,則把已經(jīng)加上的鎖釋放 -- 破壞請(qǐng)求與保持
??????????????? (請(qǐng)求不到新的,則釋放已有的)
避免:銀行家算法、死鎖檢測(cè)算法……
銀行家算法
http://t.csdn.cn/1YxZj
三、條件變量實(shí)現(xiàn)同步
1)概念
同步:通過(guò)條件控制,保證資源訪問(wèn)的合理性
條件變量:主要是一個(gè)pcb等待隊(duì)列,以及喚醒和阻塞線程的接口
原理:
??????? 線程1 獲取資源時(shí)進(jìn)行判斷,如果線程不符合資源獲取條件,則調(diào)用阻塞接口進(jìn)行阻塞
??????? 線程2 促使資源獲取條件滿(mǎn)足之后(生產(chǎn)資源),通過(guò)喚醒接口喚醒阻塞的線程
條件變量需要搭配互斥鎖來(lái)使用
舉例:
??????? 顧客 與 廚師 (消費(fèi)者與生產(chǎn)者)
顧客來(lái)到柜臺(tái),看到柜臺(tái)有飯則吃飯,否則阻塞
廚師來(lái)到柜臺(tái),看到柜臺(tái)上沒(méi)有飯則做飯,否則阻塞
顧客:
??????? 0、加鎖(關(guān)門(mén))
??????? 1、訪問(wèn)柜臺(tái)有沒(méi)有飯
??????????????? 有飯則吃飯,沒(méi)有飯就阻塞
???????????? (這里阻塞時(shí)需要進(jìn)行解鎖,否則廚師就無(wú)法訪問(wèn)柜臺(tái),也就無(wú)法做飯,產(chǎn)生死鎖)
??????????????? 阻塞則需先解鎖,再阻塞,被喚醒之后再加鎖
??????? 2、吃飯
??????? 3、吃完了,再來(lái)一碗?????? 喚醒廚師
??????? 4、解鎖
廚師:
??????? 0、加鎖
??????? 1、訪問(wèn)柜臺(tái)有沒(méi)有飯
??????? ??????? 沒(méi)飯則做飯,有飯則阻塞
??????????????? (這里是有飯就需要解鎖,讓顧客能夠吃飯,否則會(huì)死鎖)
??????????????? 阻塞需先解鎖,再阻塞,被喚醒后加鎖
??????? 2、做飯
??????? 3、做好了,喚醒顧客
??????? 4、解鎖
2)接口
定義條件變量:
??????? pthread_cond_t 條件變量的變量類(lèi)型
初始化條件變量:
??????? pthread_cond_t cond_init (pthread_cond_t *cond, pthread_condattr_t *attr);
阻塞接口:條件變量是搭配互斥鎖一起使用的,就體現(xiàn)在阻塞這一步
????????int pthread_cond_wait (pthread_cond_t *cond, pthread_mutex_t *mutex) -- 阻塞接口
??????? int pthread_cond_timewait(pthread_cond_t *cond, pthread_mutex_t *mutex,
????????????????????????????????????????????????? struct timespec *t ) -- 有時(shí)長(zhǎng)限制的阻塞
喚醒接口:
??????? int pthread_cond_signal(pthread_cond_t *cond)??????? 喚醒至少一個(gè)阻塞隊(duì)列中的線程
??????? int pthread_cond_broadcast(pthread_cond_t *cond)??????? 喚醒等待隊(duì)列中所有的線程
銷(xiāo)毀接口:
??????? int pthread_cond_destroy(pthread_cond_t *cond)
3)模擬實(shí)現(xiàn)
#include<stdio.h>
#include<pthread.h>int counter = 0; // 定義柜臺(tái)狀態(tài) 0——沒(méi)飯 1——有飯
pthread_mutex_t mutex; // 初始化鎖
pthread_cond_t cond; // 初始化條件變量void* customer(void *arg)
{while(1){pthread_mutex_lock(&mutex); // 先加鎖if(counter==0){pthread_cond_wait(&cond, &mutex); // 沒(méi)飯則解鎖 并阻塞,等待喚醒,喚醒后加鎖}printf("真好吃,再來(lái)一碗\n");counter = 0;pthread_cond_signal(&cond); // 吃完了喚醒廚師做飯pthread_mutex_unlock(&mutex); // 解鎖}
}void* cook(void *arg)
{while(1){pthread_mutex_lock(&mutex); // 加鎖if(counter == 1){pthread_cond_wait(&cond, &mutex); // 有飯則 解鎖并阻塞,等待喚醒,喚醒后加鎖}printf("你的飯好了\n");counter = 1;pthread_cond_signal(&cond); // 飯做好了喚醒顧客吃飯pthread_mutex_unlock(&mutex); // 解鎖}
}int main()
{pthread_mutex_init(&mutex, NULL); // 初始化定義mutex 和 condpthread_cond_init(&cond, NULL);pthread_t cook_tid; // 初始化定義線程ID(顧客和廚師)pthread_t cus_tid;int ret;ret = pthread_create(&cook_tid, NULL, cook, NULL); // 分別創(chuàng)建對(duì)應(yīng)線程if(ret != 0){perror("create error");return -1;}ret = pthread_create(&cus_tid, NULL, customer, NULL);if(ret != 0){perror("create error");return -1;}pthread_join(cook_tid, NULL); // 執(zhí)行線程等待pthread_join(cus_tid, NULL);pthread_mutex_destroy(&mutex); // 執(zhí)行mutex與cond的銷(xiāo)毀pthread_cond_destroy(&cond);
}
?實(shí)現(xiàn)四個(gè)顧客四個(gè)廚師
在同步實(shí)現(xiàn)的代碼中,如果存在多種角色,就應(yīng)該定義多個(gè)條件變量,各自處于各自的pcb等待隊(duì)列中。
#include<stdio.h>
#include<pthread.h>int counter = 0;
pthread_mutex_t mutex;
pthread_cond_t cond_cus; // 使用不同的條件變量,防止死鎖
pthread_cond_t cond_cook;
void* customer(void *arg)
{while(1){pthread_mutex_lock(&mutex);while(counter <= 0){pthread_cond_wait(&cond_cus, &mutex);}printf("真好吃,再來(lái)一碗: %d\n",counter);counter--;pthread_cond_signal(&cond_cook);pthread_mutex_unlock(&mutex);}
}void* cook(void *arg)
{while(1){pthread_mutex_lock(&mutex);while(counter > 0){pthread_cond_wait(&cond_cook, &mutex);}printf("你的飯好了:%d\n", counter);counter++;pthread_cond_signal(&cond_cus);pthread_mutex_unlock(&mutex);}
}int main()
{pthread_mutex_init(&mutex, NULL);pthread_cond_init(&cond_cus, NULL);pthread_cond_init(&cond_cook, NULL);pthread_t cook_tid[4]; // 定義四個(gè)顧客、四個(gè)廚師pthread_t cus_tid[4];int ret;for(int i = 0; i < 4; i++) // 創(chuàng)建這八個(gè)線程{ret = pthread_create(&cook_tid[i], NULL, cook, NULL);if(ret != 0){perror("create error");return -1;}ret = pthread_create(&cus_tid[i], NULL, customer, NULL);if(ret != 0){perror("create error");return -1;}}pthread_join(cook_tid[0], NULL);pthread_join(cus_tid[0], NULL);pthread_mutex_destroy(&mutex);pthread_cond_destroy(&cond_cook);pthread_cond_destroy(&cond_cus);
}
?生產(chǎn)者與消費(fèi)者模型
生產(chǎn)者與消費(fèi)者模型http://t.csdn.cn/GvZlZ