文章目錄
- 死鎖
- 執行緒間的同步
- 條件變數
- 生產者與消費者模型
- 生產者與消費者模型代碼實作
- 信號量
死鎖
死鎖描述的是程式流程無法繼續推進的情況——多個執行緒對鎖資源進行爭搶獲取,但是因為流程推進順序不當造成互相等待,最終流程無法推進(程式流程因為某種原因卡死無法繼續運行);
1.死鎖四個必要條件:
(1). 互斥條件:同一時間只有一個執行緒/行程可以訪問操作;
(2). 不可剝奪條件:一個執行流已獲得的資源在未使用完之前,不能強行剝奪;(我的鎖只有我能解)
(3). 請求與保持條件:一個執行流因請求資源阻塞時,對已獲得的資源保持不放;(加了A鎖請求B鎖,B請求不到,A不釋放)
(4). 環路等待條件:若干條執行流之間形成一種頭尾相接的回圈等待資源的關系;(執行緒1加了A鎖請求B鎖,執行緒2加了B鎖請求A鎖)
2.避免死鎖:
(防止上面的(3)(4))
預防死鎖:
(1)破壞死鎖的四個必要條件
(2)加/解鎖順序一致;
(3)避免鎖未釋放的場景(請求不到第二個鎖則釋放已有的鎖“非阻塞加鎖”);
(4)資源一次性分配
3.避免死鎖演算法:
- 死鎖檢測演算法;
- 銀行家演算法:https://baike.sogou.com/kexue/d11014.htm?ch=fromsearch
同步的實作:通過條件判斷對資源獲取的合理性——條件變數;
條件變數:pcb等待佇列+能夠使執行緒阻塞以及喚醒執行緒阻塞的介面;
執行緒間的同步
條件變數
條件變數實作同步:
條件變數提供了使執行緒阻塞以及喚醒阻塞執行緒的介面,但具體什么執行緒阻塞什么時候喚醒需要程式員通過條件變數控制合理的時序邏輯,只需要在合適的時候呼叫條件變數提供的阻塞/喚醒介面
條件變數使用注意事項:
- 條件判斷必須使用回圈操作,避免喚醒多個執行緒卡在鎖上,解鎖后資源訪問不合理的情況下進行資源訪問;
- 多種角色應該使用多個條件變數,讓不同的角色等待在不同的條件變數佇列中,便于角色喚醒,防止喚醒角色錯誤的情況;
1. 定義條件變數變數:
pthread_cond_t cond;
2.初始化:
int pthread_cond_init(pthread_cond_t *restrict cond, const pthread_condattr_t *restrict attr);
cond:要初始化的條件變數; attr:NULL
3. 在一個執行緒不滿足資源訪問的情況下,使執行緒阻塞:
int pthread_cond_wait(pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex);
.
int pthread_cond_timedwait(pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex, struct timespec *timeout);
4.在資源訪問條件滿足之后,喚醒阻塞的執行緒:
int pthread_cond_broadcast(pthread_cond_t *cond);——全部喚醒
int pthread_cond_signal(pthread_cond_t *cond);——喚醒至少一個
5. 銷毀條件變數:
int pthread_cond_destory(pthread_cond_t *cond);
生產者與消費者模型
使用場景:大量資料產出以及處理的場景;
優點:
1.解耦合:模塊分離降低功能耦合度;
2.支持并發:支持多對多,前提是中間緩沖區的操作是必須執行緒安全的;
3.支持忙閑不均:中間緩沖區在壓力大的時候緩沖資料在閑的時候處理;
實作一個執行緒安全的阻塞佇列類,其中包含佇列以及對外介面(入隊,出隊);
一個場景下,兩個角色、三種關系(1.生產者——生產者《互斥》2.消費者——消費者《互斥》3.生產者——消費者《同步+互斥》)
//實作
class BlockQueue
{
private:
std::queue<int> _queue;
pthread _cond_t producer_cond; //生產者等待的條件變數
pthread_cond_t consumer_cond; //消費者等待的條件變數
pthread_mutex_t _mutex; //互斥鎖保護_queue的操作
public:
BlockQueue() {}
bool Pop(int *data) {} //執行緒安全的出隊介面
bool Push(const int data) {} //執行緒安全的入隊介面
~BlockQueue() {}
};
生產者與消費者模型代碼實作
1 #include<iostream>
2 #include<stdio.h>
3 #include<queue>
4 #include<pthread.h>
5 using namespace std;
6 #define MAX_QUEUE 5
7 #define NUM_MEMBER 4
8 class blockQueue{
9 private:
10 int _capacity;
11 std::queue<int> _queue;
12 pthread_mutex_t _mutex;
13 pthread_cond_t producer_cond;
14 pthread_cond_t consumer_cond;
15 public:
16 blockQueue(int cap=MAX_QUEUE)
17 :_capacity(cap)
18 {
19 pthread_mutex_init(&_mutex,NULL);
20 pthread_cond_init(&producer_cond,NULL);
21 pthread_cond_init(&consumer_cond,NULL);
22 }
23
24 ~blockQueue()
25 {
26 pthread_mutex_destroy(&_mutex);
27 pthread_cond_destroy(&producer_cond);
28 pthread_cond_destroy(&consumer_cond);
29 }
30
31 bool Push(int data)
32 {
33 pthread_mutex_lock(&_mutex);
34 while(_capacity==_queue.size()){
35 pthread_cond_wait(&producer_cond,&_mutex);
36 }
37 _queue.push(data);
38 pthread_cond_signal(&consumer_cond);
39 pthread_mutex_unlock(&_mutex);
40 return true;
41
42 }
43
44 bool Pop(int *data)
45 {
46 pthread_mutex_lock(&_mutex);
47 while(_queue.empty()){
48 pthread_cond_wait(&consumer_cond,&_mutex);
49 }
50 *data=_queue.front();
51 _queue.pop();
52 pthread_cond_signal(&producer_cond);
53 pthread_mutex_unlock(&_mutex);
54 return true;
55 }
56 };
57
58 void *producer(void* arg)
59 {
60 blockQueue* q=(blockQueue*)arg;
61 int i=0;
62 while(1){
63 q->Push(i);
64 cout<<pthread_self()<<"push data:"<<i++<<endl;
65
66 }
67 return NULL;
68 }
69 void *consumer(void* arg)
70 {
71 blockQueue* q=(blockQueue*)arg;
72 while(1){
73 int data;
74 q->Pop(&data);
75 cout<<pthread_self()<<"get data"<<data<<endl;
76 }
77 return NULL;
78 }
79 int main()
80 {
81 blockQueue q;
82 int count=NUM_MEMBER;
83 int ret=0;
84 pthread_t pid[NUM_MEMBER];
85 pthread_t cid[NUM_MEMBER];
86 int i=0;
87 while(i<NUM_MEMBER){
88 ret=pthread_create(&pid[i],NULL,producer,&q);{
89 if(ret!=0){
90 printf("創建失敗\n");
91 return -1;
92 }
93 }
94 ++i;
95 }
96 int j=0;
97 while(j<NUM_MEMBER){
98 ret=pthread_create(&cid[i],NULL,consumer,&q);{
99 if(ret!=0){
100 cout<<"創建失敗"<<endl;
101 //printf("創建失敗\n");
102 return -1;
103 }
104 j++;
105 }
106 int count_1;
107 while(count_1<NUM_MEMBER){
108 pthread_join(pid[i],NULL);
109 pthread_join(cid[i],NULL);
110 ++count_1;
111 }
112 }
113 return 0;
114 }
信號量
本質:信號量本質是一個計數器,提供pcb等待佇列和阻塞介面和喚醒介面;
功能:實作行程間/執行緒間同步、互斥;
互斥實作:通過計數器表示資源只有一個,保證同一時間只有一個執行緒/行程訪問;
同步實作:通過同一時間對資源進行計數,進而判斷對以資源的訪問是否合理;
P操作:在資源訪問之前-1,判斷是否合理,不合理則阻塞,合理則回傳;
V操作:在產生資源之后+1,喚醒阻塞的執行緒/行程;
1.定義信號量:sem_t sem;
2.初始化信號量:int sem_init(sem_t *sem, int pshared, unsigned int value);
3.等待信號量 在臨界資源訪問/獲取之前進行P操作:
int sem_wait(sem_t *sem); ——阻塞介面,當前計數小于0則阻塞int sem_trywait(sem_t *sem); ——非阻塞介面,當前計數小于0則報錯回傳
int sem_timedwait(sem_t *sem, const struct timespec *timeout); ——限時阻塞操作,在指定時間內若無法滿足條件超時則報錯回傳
回傳值:成功的情況下計數-1,成功回傳;錯誤回傳-1,錯誤原因需要通過errno確定(EINTR - 表示阻塞被信號打斷;ETIMEDOUT - 表示tinedwait等待超時;EAGAIN - 表示trywait非阻塞情況下計數為0)
4.發布信號量 資源產生后,計數+1,喚醒阻塞(V操作)
int sem_post(sem_t *sem);5.銷毀信號量 int sem_destory(sem_t *sem);
- 信號量與條件變數的不同:
信號量是通過自身計數實作判斷,不需要搭配互斥鎖,條件變數需要程式員自己進行條件判斷,使用時需要搭配互斥鎖;
- 信號量與互斥鎖的不同:
信號量可以實作互斥,但是更多的是用于同步的實作
多執行緒:https://blog.csdn.net/weixin_52270223/article/details/115820547?spm=1001.2014.3001.5502
執行緒安全:https://blog.csdn.net/weixin_52270223/article/details/115835481?spm=1001.2014.3001.5502
如有錯誤或者補充,評論下;互相學習,互關一波,抱拳了
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/279549.html
標籤:其他
上一篇:ARP欺騙原理及實驗
