📚?博主的專欄
🐧?Linux???|?? 🖥??C++???|?? 📊?數據結構??|?💡C++ 算法?| 🌐?C 語言
進程是資源分配的基本單位,線程是調度的基本單位,線程是在進程內部運行的(是進程內部的控制序列,本質是在進程地址空間內運行),如何理解線程是在進程內部運行的?因為在linux中,線程是用進程pcb來模擬的,所有的PCB共享虛擬地址空間,共享頁表。
上篇文章:線程id、互斥
下篇文章:POSIX信號量、基于環形隊列的生產消費模型、線程池
目錄
條件變量
同步概念與競態條件
a.核心接口函數
1. 初始化條件變量
2. 銷毀條件變量
3. 等待條件變量
4. 限時等待
5. 喚醒單個線程
6. 喚醒所有線程
?b.認識條件變量(舉例)
場景設定
為什么 pthread_cond_wait 需要互斥量?
錯誤的設計?
條件變量使用規范
c.生產消費模型
基于BlockingQueue的生產者消費者模型
完整代碼:
條件變量
當一個線程互斥地訪問某個變量時,它可能發現在其它線程改變狀態之前,它什么也做不了。
例如一個線程訪問隊列時,發現隊列為空,它只能等待,只到其它線程將一個節點添加到隊列中。這種情況就需要用到條件變量。
同步概念與競態條件
同步:在保證數據安全的前提下,讓線程能夠按照某種特定的順序訪問臨界資源,從而有效避免饑餓問題,叫做同步
競態條件:因為時序問題,而導致程序異常,我們稱之為競態條件。在線程場景下,這種問題也不難理解
a.核心接口函數
類似于線程鎖的接口
1. 初始化條件變量
int pthread_cond_init(pthread_cond_t *cond, const pthread_condattr_t *attr);
功能:動態初始化條件變量。
參數:
cond
:指向條件變量的指針。
attr
:屬性參數,通常設為NULL
(使用默認屬性)。靜態初始化(無需調用
init
):pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
2. 銷毀條件變量
int pthread_cond_destroy(pthread_cond_t *cond);
功能:釋放條件變量占用的資源。
注意事項:確保沒有線程在等待該條件變量后再調用此函數。
3. 等待條件變量
int pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex);
功能:阻塞當前線程,直到其他線程通過
signal
或broadcast
喚醒它。關鍵行為:
原子操作:調用時會自動釋放關聯的互斥鎖,并進入等待狀態。
被喚醒后:函數返回前會重新獲取互斥鎖。
使用模式(必須與互斥鎖配合):
pthread_mutex_lock(&mutex); while (條件不滿足) { // 必須用循環檢查條件,防止虛假喚醒pthread_cond_wait(&cond, &mutex); } // 執行臨界區操作 pthread_mutex_unlock(&mutex);
4. 限時等待
int pthread_cond_timedwait(pthread_cond_t *cond, pthread_mutex_t *mutex, const struct timespec *abstime);
功能:與
pthread_cond_wait
類似,但可設置超時時間。參數:
abstime
是絕對時間(例如clock_gettime(CLOCK_REALTIME, &ts)
獲取當前時間后加上超時偏移)。5. 喚醒單個線程
int pthread_cond_signal(pthread_cond_t *cond);
功能:喚醒至少一個正在等待該條件變量的線程(具體喚醒哪個線程取決于調度策略)。
6. 喚醒所有線程
int pthread_cond_broadcast(pthread_cond_t *cond);
功能:喚醒所有正在等待該條件變量的線程。
?b.認識條件變量(舉例)
主線程喚醒新線程,或全部喚醒
場景設定
假設有一家網紅冰淇淋店,每天限量供應10個冰淇淋。顧客(線程)絡繹不絕,但規則是:
每次只能有一個顧客購買(互斥鎖保護)。
如果冰淇淋賣完了,顧客必須排隊等待,直到店員補貨(條件變量通知)。
補貨后,所有等待的顧客可以重新嘗試購買。
#include <pthread.h>// 全局變量
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; // 互斥鎖:控制購買權
pthread_cond_t cond = PTHREAD_COND_INITIALIZER; // 條件變量:通知補貨
int ice_cream = 0; // 當前冰淇淋數量(初始為0,需補貨)// 店員線程(補貨)
void* staff(void* arg) {while (1) {pthread_mutex_lock(&mutex);if (ice_cream == 0) { // 如果冰淇淋賣光了ice_cream = 10; // 補貨10個printf("店員:新到貨10個冰淇淋!\n");pthread_cond_broadcast(&cond); // 大喊一聲通知所有顧客}pthread_mutex_unlock(&mutex);sleep(5); // 每隔5秒檢查一次是否需要補貨}
}// 顧客線程(購買)
void* customer(void* arg) {int id = *(int*)arg;pthread_mutex_lock(&mutex);while (ice_cream == 0) { // 如果沒貨了printf("顧客%d:\"淦,賣完了?我等!\"\n", id);pthread_cond_wait(&cond, &mutex); // 坐下等待店員喊補貨}ice_cream--; // 成功購買printf("顧客%d:搶到一個冰淇淋!剩余:%d\n", id, ice_cream);pthread_mutex_unlock(&mutex);return NULL;
}int main() {pthread_t staff_thread;pthread_t customer_threads[15];int ids[15];// 啟動店員線程(持續補貨)pthread_create(&staff_thread, NULL, staff, NULL);// 模擬15個顧客瘋狂搶購for (int i=0; i<15; i++) {ids[i] = i;pthread_create(&customer_threads[i], NULL, customer, &ids[i]);usleep(200000); // 讓顧客們陸續到達}// 等待所有線程結束for (int i=0; i<15; i++) pthread_join(customer_threads[i], NULL);return 0;
}
互斥鎖(mutex):
相當于“購買權”,一次只允許一個顧客查看/購買冰淇淋。
顧客進店前必須搶到鎖(
pthread_mutex_lock
)。條件變量(cond):
當冰淇淋賣光時(
ice_cream == 0
),顧客調用pthread_cond_wait
進入等待隊列,同時釋放鎖(讓其他顧客可以繼續嘗試)。pthread_cond_wait在被調用的時候,除了讓自己繼續排隊等待,還會自己釋放傳入的鎖。店員補貨后,通過
pthread_cond_broadcast
喊一嗓子:“補貨啦!”,喚醒所有等待的顧客。循環檢查條件(
while
而非if
):
假設10個顧客被喚醒,但只有前10個能買到,第11個發現
ice_cream
又變0了,繼續等待。防止虛假喚醒(比如被意外吵醒但沒補貨)。
顧客0:搶到一個冰淇淋!剩余:9
顧客1:搶到一個冰淇淋!剩余:8
...(前10個顧客購買成功)
顧客10:\"淦,賣完了?我等!\"
店員:新到貨10個冰淇淋!
顧客10:搶到一個冰淇淋!剩余:9
顧客11:搶到一個冰淇淋!剩余:8
...
為什么 pthread_cond_wait 需要互斥量?
條件等待是線程間同步的一種手段,如果只有一個線程,條件不滿足,一直等下去都不會滿足,所以必須要有一個線程通過某些操作,改變共享變量,使原先不滿足的條件變得滿足,并且友好的通知等待在條件變量上的線程。
條件不會無緣無故的突然變得滿足了,必然會牽扯到共享數據的變化。所以一定要用互斥鎖來保護。沒有互斥鎖就無法安全的獲取和修改共享數據。?
錯誤的設計?
// 錯誤的設計 pthread_mutex_lock(&mutex); while (condition_is_false) { pthread_mutex_unlock(&mutex); //解鎖之后,等待之前,條件可能已經滿足,信號已經發出,但是該信號可能被錯過 pthread_cond_wait(&cond); pthread_mutex_lock(&mutex); } p thread_mutex_unlock(&mutex);
由于解鎖和等待不是原子操作。調用解鎖之后, pthread_cond_wait 之前,如果已經有其他線程獲取到互斥量,摒棄條件滿足,發送了信號,那么 pthread_cond_wait 將錯過這個信號,可能會導致線程永遠阻塞在這個 pthread_cond_wait 。所以解鎖和等待必須是一個原子操作。
int pthread_cond_wait(pthread_cond_ t *cond,pthread_mutex_ t * mutex); 進入該函數后,會去看條件量等于0?等于,就把互斥量變成1,直到cond_ wait返回,把條件量改成1,把互斥量恢復成原樣。后面繼續以生產者消費者模型來深入講解。請特別注意代碼中的注釋
條件變量使用規范
等待條件代碼
pthread_mutex_lock(&mutex); while (條件為假) pthread_cond_wait(cond, mutex);
修改條件
?pthread_mutex_unlock(&mutex); 給條件發送信號代碼 pthread_mutex_lock(&mutex);
設置條件為真
?pthread_cond_signal(cond); pthread_mutex_unlock(&mutex)
c.生產消費模型
生產者消費者就是一個多執行流并發的模型
為何要使用生產者消費者模型
生產者消費者模式就是通過一個容器來解決生產者和消費者的強耦合問題。生產者和消費者彼此之間不直接通訊,而通過阻塞隊列來進行通訊,所以生產者生產完數據之后不用等待消費者處理,直接扔給阻塞隊列,消費者不找生產者要數據,而是直接從阻塞隊列里取,阻塞隊列就相當于一個緩沖區,平衡了生產者和消費者的處理能力。這個阻塞隊列就是用來給生產者和消費者解耦的。
生產者消費者模型優點
(供應商、超市(一段內存)方便面(數據)、消費者的例子)
- 解耦
- 支持并發
- 支持忙閑不均
思考切入點:“321”原則
例如面試時問到生產者消費者模型:就按照321介紹,再說生產者消費者模型優缺點。
? ? ? ? 1.一個交易場所(特定數據結構形式存在的一段內存空間)
? ? ? ? 2.兩種角色(生產角色,消費角色)生產線程,和消費線程
? ? ? ? 3.三種關系(生產和生產、消費和消費、生產和消費)
例如供應商(生產者)都是競爭關系,消費者之間是互斥關系(想一想如果商品出現供不應求),生產者和消費者(供應商正在商品價上放商品還未標價)之間是互斥關系,并且還要維護一定程度的(如果出現供大于求,提醒消費者來買,或者供不應求,消費者提醒供貨商出貨)同步關系。
如果我要實現生產消費模型,本質就是通過代碼,實現321原則,用鎖和條件變量(或其他方式)來實現三種關系。
基于BlockingQueue的生產者消費者模型
BlockingQueue
在多線程編程中阻塞隊列(Blocking Queue)是一種常用于實現生產者和消費者模型的數據結構。其與普通的隊列區別在于,當隊列為空時,從隊列獲取元素的操作將會被阻塞,直到隊列中被放入了元素;當隊列滿時,往隊列里存放元素的操作也會被阻塞,直到有元素被從隊列中取出(以上的操作都是基于不同的線程來說的,線程在對阻塞隊列進程操作時會被阻塞)
C++ queue模擬阻塞隊列的生產消費模型
實際上管道(管道自帶同步和互斥關系)也是一種生產消費模型。
首先我們看這個代碼:當在使用條件變量線程在等待的時候,會將鎖釋放,但是當wait結束,函數返回,沒有鎖還是在臨界區。實際上,在函數返回時,必須先參與鎖的競爭,重新申請到鎖,該函數才會返回。因此醒來的時候也是有鎖的。
void Equeue(const T& in){pthread_mutex_lock(&_mutex);if(IsFull())//隊列是否滿了{//生產這不能生產,必須等待//在臨界區里面在等待,但鎖沒有釋放,即便等待了,但是鎖沒有釋放,注定消費者pop的時候也無法拿到鎖,死鎖pthread_cond_wait(&_p_cond, &_mutex);//函數返回的時候,不還在臨界區嗎,并且鎖不是已經被釋放了嗎//pthread_cond_wait在被調用的時候,除了讓自己繼續排隊等待,還會自己釋放傳入的鎖}pthread_mutex_unlock(&_mutex);}
生產者生產后,一定知道隊列不為空,消費者消費一個后一定知道隊列沒有滿,因此生產者和消費者是互相喚醒的,互相通知。
//將數據帶出去的接口:void Pop(T* out){pthread_mutex_lock(&_mutex);if(IsEmpty()){pthread_cond_wait(&_c_cond, &_mutex);//等待喚醒}//1.沒有空 || 2.被喚醒了//說明一定有數據,因此要消費*out = _block_queue.front();_block_queue.pop();//消費者消費一個數據,就一定知道隊列不是滿的//生產者最清楚隊列一定不為空pthread_mutex_unlock(&_mutex);pthread_cond_signal(&_p_cond);//生產者沒睡著,此操作無效}//入隊列void Equeue(const T& in){pthread_mutex_lock(&_mutex);if(IsFull())//隊列是否滿了{//生產這不能生產,必須等待//在臨界區里面在等待,但鎖沒有釋放,即便等待了,但是鎖沒有釋放,注定消費者pop的時候也無法拿到鎖,死鎖pthread_cond_wait(&_p_cond, &_mutex);//函數返回的時候,不還在臨界區嗎,并且鎖不是已經被釋放了嗎//等待喚醒//pthread_cond_wait在被調用的時候,除了讓自己繼續排隊等待,還會自己釋放傳入的鎖}// 1.沒有滿 || 2.被喚醒了//因此就可以生產到阻塞隊列(還沒解鎖,數據不會被拿走,一定是有數據的)_block_queue.push(in);pthread_mutex_unlock(&_mutex);//有數據了,就要讓消費者消費pthread_cond_signal(&_c_cond);//消費者沒睡著,此操作無效,}
為什么喚醒操作放在解鎖后
都是可以的,一旦把任意一方喚醒,他們都仍然需要競爭鎖,因為在等待的時候他們都將鎖釋放了,如果競爭鎖失敗了,就會繼續等待,等另外線程釋放鎖,再返回。局部互斥整體同步。
可以設置一個策略,到達設定的容量的時候就通知。
int low_water????????????????int high_water
條件尚未滿足,但是線程被異常喚醒的請款,叫做偽喚醒,沒有競爭成功的線程會再次判斷隊列是否為空,為空就再次等待,不為空再繼續,再進行獲取資源,因此我們用到while,可以保證代碼的魯棒性(健壯性)
如果,生產者只有一個,消費者有兩個,當兩個消費者都在等待的時候,生產者采用
pthread_cond_broadcast()喚醒線程怎么辦(此時只有一個能提供給消費者消費的資源)
兩個消費者都被喚醒了,其中一個競爭鎖失敗的線程在等待(不在條件變量下等),另一個線程在鎖那里等,等拿了資源的線程釋放鎖,等拿了資源的線程釋放鎖之后,就會持有到鎖,就會繼續往后走,但是隊列已經沒有資源了。
完整代碼:
阻塞隊列封裝1.0
#pragma once#include <iostream>
#include <string>
#include <queue>
#include <pthread.h>const static int defaultcap = 5;
template <class T>
class BlockQueue
{
private:bool IsFull(){return _block_queue.size() == _max_cap;}bool IsEmpty(){return _block_queue.empty();}public:BlockQueue(int cap = defaultcap): _max_cap(cap){pthread_mutex_init(&_mutex, nullptr);pthread_cond_init(&_p_cond, nullptr);pthread_cond_init(&_c_cond, nullptr);}// 將數據帶出去的接口:void Pop(T *out){pthread_mutex_lock(&_mutex);if (IsEmpty()){pthread_cond_wait(&_c_cond, &_mutex); // 等待喚醒}// 1.沒有空 || 2.被喚醒了// 說明一定有數據,因此要消費*out = _block_queue.front();_block_queue.pop(); // 消費者消費一個數據,就一定知道隊列不是滿的// 生產者最清楚隊列一定不為空pthread_mutex_unlock(&_mutex);pthread_cond_signal(&_p_cond); // 生產者沒睡著,此操作無效}// 入隊列void Equeue(const T &in){pthread_mutex_lock(&_mutex);if (IsFull()) // 隊列是否滿了{// 生產這不能生產,必須等待// 在臨界區里面在等待,但鎖沒有釋放,即便等待了,但是鎖沒有釋放,注定消費者pop的時候也無法拿到鎖,死鎖pthread_cond_wait(&_p_cond, &_mutex); // 函數返回的時候,不還在臨界區嗎,并且鎖不是已經被釋放了嗎// 等待喚醒// pthread_cond_wait在被調用的時候,除了讓自己繼續排隊等待,還會自己釋放傳入的鎖}// 1.沒有滿 || 2.被喚醒了//因此就可以生產到阻塞隊列(還沒解鎖,數據不會被拿走,一定是有數據的)_block_queue.push(in);pthread_mutex_unlock(&_mutex);// 有數據了,就要讓消費者消費pthread_cond_signal(&_c_cond); // 消費者沒睡著,此操作無效,// 如果,生產者只有一個,消費者有兩個,當兩個消費者都在等待的時候,生產者采用// pthread_cond_broadcast()喚醒線程怎么辦(此時只有一個能提供給消費者消費的資源}~BlockQueue(){pthread_mutex_destroy(&_mutex);pthread_cond_destroy(&_p_cond);pthread_cond_destroy(&_c_cond);}private:std::queue<T> _block_queue; // 臨界資源int _max_cap; // 隊列最大值pthread_mutex_t _mutex; // 定義一把鎖pthread_cond_t _p_cond; // 給生產者提供的條件變量pthread_cond_t _c_cond; // 給消費者提供的條件變量
};
生產者消費者1.0:
#include"BlockQueue.hpp"
#include<pthread.h>
//做隨機值
#include<ctime>
#include<unistd.h>
//消費者
void *Consumer(void *args)
{BlockQueue<int>* bq = static_cast<BlockQueue<int> *>(args);//生產者、消費者看到同一個隊列while(true){sleep(2);int data = 0;//1.獲取數據:從阻塞隊列中把任務拿走bq->Pop(&data);//2.處理數據std::cout<<"Consumer ->" << data << std::endl;}
}
//生產者
void* Productor(void* args)
{srand(time(nullptr) ^ getpid());BlockQueue<int>* bq = static_cast<BlockQueue<int> *>(args);//生產者、消費者看到同一個隊列while(true){//1.構建數據int data = rand()% 10 + 1;//[1,10]//2.生產數據bq->Equeue(data);std::cout << "Producer -->" << data << std::endl; }
}int main()
{BlockQueue<int> *bq = new BlockQueue<int>();pthread_t c, p;pthread_create(&c, nullptr, Consumer, bq);pthread_create(&p, nullptr, Productor, bq);pthread_join(c, nullptr);pthread_join(p, nullptr);return 0;
}
此時的代碼是,生產者一直生產,消費者2s消費一次:因為產品飽和,后續就需要消費者消費一次再生產一次。因為是阻塞隊列
當我們讓生產者2s生產一個,消費者一直消費:就會出現生產者生產一個消費者消費一個的現象:
?阻塞隊列除了傳整數還能傳
既然像阻塞隊列當中投遞數據,就也能投遞任務:
既然是傳模版,那么就能傳自定義類,我們將結構化數據封裝成任務,再傳給阻塞隊列。
封裝一個任務類:
class Task
{
public:Task(){}Task(int x, int y) : _x(x), _y(y){}// 做加法void Excute(){_result = _x + _y;}void operator()(){Excute();}std::string debug(){std::string msg = std::to_string(_x) + "+" + std::to_string(_y) + "=?";return msg;}std::string result(){std::string msg = std::to_string(_x) + "+" + std::to_string(_y) + "=" + std::to_string(_result);return msg;}private:int _x;int _y;int _result;
};
阻塞隊列封裝2.0,就是修改傳的模版參數以及獲取數據
#pragma once#include <iostream>
#include <string>
#include <queue>
#include <pthread.h>
#include "Task.hpp"
const static int defaultcap = 5;
template <class T>
class BlockQueue
{
private:bool IsFull(){return _block_queue.size() == _max_cap;}bool IsEmpty(){return _block_queue.empty();}public:BlockQueue(int cap = defaultcap): _max_cap(cap){pthread_mutex_init(&_mutex, nullptr);pthread_cond_init(&_p_cond, nullptr);pthread_cond_init(&_c_cond, nullptr);}// 將數據帶出去的接口:void Pop(T *out){pthread_mutex_lock(&_mutex);if (IsEmpty()){pthread_cond_wait(&_c_cond, &_mutex); // 等待喚醒}// 1.沒有空 || 2.被喚醒了// 說明一定有數據,因此要消費*out = _block_queue.front();_block_queue.pop(); // 消費者消費一個數據,就一定知道隊列不是滿的// 生產者最清楚隊列一定不為空pthread_mutex_unlock(&_mutex);pthread_cond_signal(&_p_cond); // 生產者沒睡著,此操作無效}// 入隊列void Equeue(const T &in){pthread_mutex_lock(&_mutex);if (IsFull()) // 隊列是否滿了{// 生產這不能生產,必須等待// 在臨界區里面在等待,但鎖沒有釋放,即便等待了,但是鎖沒有釋放,注定消費者pop的時候也無法拿到鎖,死鎖pthread_cond_wait(&_p_cond, &_mutex); // 函數返回的時候,不還在臨界區嗎,并且鎖不是已經被釋放了嗎// 等待喚醒// pthread_cond_wait在被調用的時候,除了讓自己繼續排隊等待,還會自己釋放傳入的鎖}// 1.沒有滿 || 2.被喚醒了//因此就可以生產到阻塞隊列(還沒解鎖,數據不會被拿走,一定是有數據的)_block_queue.push(in);pthread_mutex_unlock(&_mutex);// 有數據了,就要讓消費者消費pthread_cond_signal(&_c_cond); // 消費者沒睡著,此操作無效,// 如果,生產者只有一個,消費者有兩個,當兩個消費者都在等待的時候,生產者采用// pthread_cond_broadcast()喚醒線程怎么辦(此時只有一個能提供給消費者消費的資源}~BlockQueue(){pthread_mutex_destroy(&_mutex);pthread_cond_destroy(&_p_cond);pthread_cond_destroy(&_c_cond);}private:std::queue<T> _block_queue; // 臨界資源int _max_cap; // 隊列最大值pthread_mutex_t _mutex; // 定義一把鎖pthread_cond_t _p_cond; // 給生產者提供的條件變量pthread_cond_t _c_cond; // 給消費者提供的條件變量
};
?生產消費:
#include"BlockQueue.hpp"
#include<pthread.h>
//做隨機值
#include<ctime>
#include<unistd.h>
//消費者
void *Consumer(void *args)
{BlockQueue<Task>* bq = static_cast<BlockQueue<Task> *>(args);//生產者、消費者看到同一個隊列while(true){// sleep(2);// int data = 0;//1.獲取數據:從阻塞隊列中把任務拿走Task t;bq->Pop(&t);//2.處理數據t.Excute();std::cout<<"Consumer ->" << t.result() << std::endl;}
}
//生產者
void* Productor(void* args)
{srand(time(nullptr) ^ getpid());BlockQueue<Task>* bq = static_cast<BlockQueue<Task> *>(args);//生產者、消費者看到同一個隊列while(true){sleep(2);//1.構建數據int x = rand()% 10 + 1;//[1,10]usleep(x*1000);int y = rand()% 10 + 1;Task t(x, y);//2.生產數據// bq->Equeue(x, y);//臨時變量無法被引用bq->Equeue(t);std::cout << "Producer -->" << t.debug() << std::endl; }
}int main()
{BlockQueue<Task> *bq = new BlockQueue<Task>();pthread_t c, p;pthread_create(&c, nullptr, Consumer, bq);pthread_create(&p, nullptr, Productor, bq);pthread_join(c, nullptr);pthread_join(p, nullptr);return 0;
}
運行結果:?
通過前面所學的functional包裝器來做任務傳函數對象?
// 等同于typedef std::function<void()> task_t;
using task_t = std::function<void()>;//返回值是void參數為空的函數對象,類似于一個類,因為底層是仿函數
在未來我們就能這樣寫;
void Download()
{std::cout << "我是一個下載的任務" << std::endl;
}
生產者消費者3.0?
#include"BlockQueue.hpp"
#include<pthread.h>
//做隨機值
#include<ctime>
#include<unistd.h>
#include "Task.hpp"
//消費者
void *Consumer(void *args)
{BlockQueue<task_t>* bq = static_cast<BlockQueue<task_t> *>(args);//生產者、消費者看到同一個隊列while(true){// sleep(2);// int data = 0;//1.獲取數據:從阻塞隊列中把任務拿走// Task t;task_t t;bq->Pop(&t);//2.處理數據// t.Excute();t();// std::cout<<"Consumer ->" << t.result() << std::endl;}
}
//生產者
void* Productor(void* args)
{srand(time(nullptr) ^ getpid());BlockQueue<task_t>* bq = static_cast<BlockQueue<task_t> *>(args);//生產者、消費者看到同一個隊列while(true){sleep(2);//1.構建數據// int x = rand()% 10 + 1;//[1,10]// usleep(x*1000);// int y = rand()% 10 + 1;// Task t(x, y);//2.生產數據// bq->Equeue(x, y);//臨時變量無法被引用bq->Equeue(Download);std::cout << "Producer --> Download" << std::endl; }
}int main()
{BlockQueue<task_t> *bq = new BlockQueue<task_t>();pthread_t c, p;pthread_create(&c, nullptr, Consumer, bq);pthread_create(&p, nullptr, Productor, bq);pthread_join(c, nullptr);pthread_join(p, nullptr);return 0;
}
多生產,多消費:
在生產消費模型當中,產生任務也要花時間,消費者處理任務需要花時間,使用多生產多消費的原因是因為,獲取任務和處理任務就會出現并發,生產者放任務到某個地方的時候和其他生產者產生任務是并發的,因此我們所說的高效,說的是,這些并發。
生產和消費不能只看,他們從阻塞隊列放數據和拿數據的時間,真實的場景下,放數據和拿數據的時間只占很小的比重,處理數據、獲取數據才占更大的比重,因此多線程在根本上是為了解決:讓處理數據和獲取數據有更好的并發度
為什么加條件變量等待pthread_cond_wait,一定是加在加鎖解鎖之間臨界區里的呢?而且還添加了要傳mutex。
因為無論是生產者還是消費者,要進行消費,訪問公共資源,要知道條件是否滿足,需要先檢測資源的狀態,這種檢測,這種查看是否滿足條件,想要查看的結果也是屬于臨界區的,就是屬于訪問臨界區,因此注定要在臨界區中設置等待。
復習system V信號量:【Linux】System V信號量與IPC資源管理簡易講解-CSDN博客
申請信號量的本質就是對公共資源的一種預定機制
以前在講進程間通信中說過,信號量是一個描述資源數目的計數器,要訪問資源時,需要先申請信號量。信號量和互斥鎖之間的差別在于:不用像互斥鎖一樣在這里先做判斷了,因為信號量本身就是一個計數器,描述臨界區中資源數目得多少,所以在進入臨界區的時候,通過申請信號量的方式來的值條件是否滿足,不滿足就阻塞。
下一篇博客將講解:POSIX信號量
結語:
? ? ? ?隨著這篇博客接近尾聲,我衷心希望我所分享的內容能為你帶來一些啟發和幫助。學習和理解的過程往往充滿挑戰,但正是這些挑戰讓我們不斷成長和進步。我在準備這篇文章時,也深刻體會到了學習與分享的樂趣。 ? ?
? ? ? ? ?在此,我要特別感謝每一位閱讀到這里的你。是你的關注和支持,給予了我持續寫作和分享的動力。我深知,無論我在某個領域有多少見解,都離不開大家的鼓勵與指正。因此,如果你在閱讀過程中有任何疑問、建議或是發現了文章中的不足之處,都歡迎你慷慨賜教? ? ?。
? ? ? ? 你的每一條反饋都是我前進路上的寶貴財富。同時,我也非常期待能夠得到你的點贊、收藏,關注這將是對我莫大的支持和鼓勵。當然,我更期待的是能夠持續為你帶來有價值的內容。