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