【Linux】條件變量、基于阻塞隊列的生產者消費者模型

📚?博主的專欄

🐧?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);
  • 功能:阻塞當前線程,直到其他線程通過signalbroadcast喚醒它。

  • 關鍵行為

    • 原子操作:調用時會自動釋放關聯的互斥鎖,并進入等待狀態。

    • 被喚醒后:函數返回前會重新獲取互斥鎖

  • 使用模式(必須與互斥鎖配合):

    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個冰淇淋。顧客(線程)絡繹不絕,但規則是:

  1. 每次只能有一個顧客購買(互斥鎖保護)。

  2. 如果冰淇淋賣完了,顧客必須排隊等待,直到店員補貨(條件變量通知)。

  3. 補貨后,所有等待的顧客可以重新嘗試購買。

#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信號量

結語:

? ? ? ?隨著這篇博客接近尾聲,我衷心希望我所分享的內容能為你帶來一些啟發和幫助。學習和理解的過程往往充滿挑戰,但正是這些挑戰讓我們不斷成長和進步。我在準備這篇文章時,也深刻體會到了學習與分享的樂趣。 ? ?

? ? ? ? ?在此,我要特別感謝每一位閱讀到這里的你。是你的關注和支持,給予了我持續寫作和分享的動力。我深知,無論我在某個領域有多少見解,都離不開大家的鼓勵與指正。因此,如果你在閱讀過程中有任何疑問、建議或是發現了文章中的不足之處,都歡迎你慷慨賜教? ? ?。

? ? ? ? 你的每一條反饋都是我前進路上的寶貴財富。同時,我也非常期待能夠得到你的點贊、收藏,關注這將是對我莫大的支持和鼓勵。當然,我更期待的是能夠持續為你帶來有價值的內容。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/bicheng/77745.shtml
繁體地址,請注明出處:http://hk.pswp.cn/bicheng/77745.shtml
英文地址,請注明出處:http://en.pswp.cn/bicheng/77745.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

32-工藝品商城小程序

技術&#xff1a; 基于 B/S 架構 SpringBootMySQLvueelementuiuniapp 環境&#xff1a; Idea mysql maven jdk1.8 node 可修改為其他類型商城 用戶端功能 1.系統首頁展示輪播圖及工藝品列表 2.分類模塊:展示產品的分類類型 3.購物車:進行商品多選結算 或者批量管理操作 4.…

SLAM | 激光SLAM中的退化問題

在激光SLAM中,判斷退化環境的核心是通過數學建模分析環境特征對位姿估計的約束能力。除了LOAM中提出的退化因子D外,還存在多種基于表達式和閾值設定的方法。以下是幾種典型方法及其實現原理: 1. 協方差矩陣特征值分析 原理:通過分析點云協方差矩陣的特征值分布,判斷環境中…

【2025最新版】火鳥門戶v8.5系統源碼+PC、H5、小程序 +數據化大屏插件

一.介紹 火鳥地方門戶系統V8.5源碼 系統包含4端&#xff1a; PCH5小程序APP 二.搭建環境 系統環境&#xff1a;CentOS、 運行環境&#xff1a;寶塔 Linux 網站環境&#xff1a;Nginx 1.2.22 MySQL 5.6 PHP-7.4 常見插件&#xff1a;fileinfo &#xff1b; redis 三.測…

PHP騰訊云人臉核身獲取NONCE ticket

參考騰訊云官方文檔&#xff1a; 人臉核身 獲取 NONCE ticket_騰訊云 前提條件&#xff0c;已經成功獲取了access token。 獲取參考文檔&#xff1a; PHP騰訊云人臉核身獲取Access Token-CSDN博客 public function getTxFaceNonceTicket($uid) {$access_token file_get_c…

多人3D游戲完整實現方案

以下是一份完整的代碼實現方案,涵蓋架構設計、核心模塊實現和部署流程。我們以 多人3D游戲 為例,結合之前討論的Nano服務端框架和Unity客戶端: 技術棧 模塊技術選型服務端Golang + Nano框架 + MongoDB客戶端Unity 2022 + C# + Mirror Networking通信協議Protobuf + WebSock…

【Linux我做主】GDB調試工具完全指南

Linux下GDB調試工具完全指南&#xff1a;25個核心命令詳解與實戰示例 github地址 有夢想的電信狗 前言 GDB&#xff08;GNU Debugger&#xff09;是Linux開發中不可或缺的調試工具&#xff0c;尤其在定位代碼邏輯錯誤和內存問題時表現卓越。本文基于實際開發經驗&#xff0…

QT中柵格模式探索

1、Qt中選擇了柵格模式&#xff0c;如下圖所示&#xff1a; 2、在進行整個大的UI界面布局時&#xff0c;需了解每個控件所需要選擇的屬性sizePolicy。 sizePolicy包含如下幾種選擇&#xff1a; 3、舉個例子&#xff1a;此時整個UI界面&#xff0c;我采用了柵格模式&#xf…

【計算機網絡】3數據鏈路層①

這篇筆記專門講數據鏈路層的功能。 2.功能 數據鏈路層的主要任務是讓幀在一段鏈路上或一個網絡中傳輸。 2.1.封裝成幀(組幀) 解決的問題:①幀定界②幀同步③透明傳輸 實現組幀的方法通常有以下種。 2.1.1.字符計數法 原理:在每個幀開頭,用一個定長計數字段來記錄該…

[區塊鏈lab2] 構建具備加密功能的Web服務端

實驗目標&#xff1a; 掌握區塊鏈中密碼技術的工作原理。在基于Flask框架的服務端中實現哈希算法的加密功能。 實驗內容&#xff1a; 構建Flash Web服務器&#xff0c;實現哈希算法、非對稱加密算法的加密功能。 實驗步驟&#xff1a; 哈希算法的應用&#xff1a;創建hash…

藍橋杯之前綴和

一維前綴 解題思路 看到“區間之和”問題&#xff0c;直接想到“前綴和” 前綴和的核心公式&#xff1a; sum[i]sum[i?1]a[i] 利用前綴和求區間和 [l,r] 的公式&#xff1a; 區間和sum[r]?sum[l?1] 解題步驟模板 輸入數組&#xff1a; 讀取數組長度 n 和查詢次數 m。 讀…

【學習筆記】計算機網絡(八)—— 音頻/視頻服務

第8章 互聯網上的音頻/視頻服務 文章目錄 第8章 互聯網上的音頻/視頻服務8.1概述8.2 流式存儲音頻/視頻8.2.1 具有元文件的萬維網服務器8.2.2 媒體服務器8.2.3 實時流式協議 RTSP 8.3 交互式音頻/視頻8.3.1 IP 電話概述8.3.2 IP電話所需要的幾種應用協議8.3.3 實時運輸協議 RTP…

【WRF運行】解決metgrid生成文件太大無內存!

目錄 方法:改變工作目錄運行 metgrid.exe參考由于我的運行內存過小,當研究區較大時,metgrid生成文件內存太大,導致每次運行都報錯,此時可更改工作目錄(空余文件夾)以運行 metgrid.exe(并非必須在wrf安裝目錄下運行!!!)。 metgrid.exe 本身不支持直接通過參數或 nam…

基于 Django 進行 Python 開發

基于 Django 進行 Python 開發涉及多個方面的知識點,以下為你詳細介紹: 1. Django 基礎 項目與應用創建 借助django-admin startproject project_name來創建新的 Django 項目。利用python manage.py startapp app_name創建新的應用。項目結構 理解項目各文件和目錄的作用,像…

【sylar-webserver】8 HOOK模塊

文章目錄 知識點HOOK實現方式非侵入式hook侵入式hook ??? 覆蓋系統調用接口獲取被全局符號介入機制覆蓋的系統調用接口 具體實現C 模板成員函數繼承 和 成員函數指針類型匹配 ?????FdCtx 和 FdManager ??判斷socket的小技巧FdCtxFdManager connect hook ?do_io模板 …

SpringAI+DeepSeek大模型應用開發——1 AI概述

AI領域常用詞匯 LLM&#xff08;LargeLanguage Model&#xff0c;大語言模型&#xff09; 能理解和生成自然語言的巨型AI模型&#xff0c;通過海量文本訓練。例子&#xff1a;GPT-4、Claude、DeepSeek、文心一言、通義干問。 G&#xff08;Generative&#xff09;生成式: 根據上…

SpringBoot 基本原理

SpringBoot 為我們做的自動配置&#xff0c;確實方便快捷&#xff0c;但一直搞不明白它的內部啟動原理&#xff0c;這次就來一步步解開 SpringBoot 的神秘面紗&#xff0c;讓它不再神秘。 目錄 SpringBootApplication 背后的秘密 Configuration ComponentScan EnableAutoC…

2025.4.17總結

工作&#xff1a;今天對需求的測試設計進行了完善&#xff0c;然后&#xff0c;對測試設計進行了評審&#xff0c;最后提了個問題單。 反思這個過程&#xff0c;要說不足的地方&#xff0c;就是評審的時候總覺得自己吐字不清晰&#xff0c;表達能力早就想提升了&#xff0c;但…

2021-11-14 C++三七二十一數

緣由c編程怎么寫&#xff0c;緊急求解-編程語言-CSDN問答 void 三七二十一數() {//緣由https://ask.csdn.net/questions/7566632?spm1005.2025.3001.5141int n 0, a 0, b 0, p 1;std::cin >> n;while (n--){std::cin >> a >> b;while (a<b){if (a %…

大模型面經 | DeepSpeed中ZeRO-1、ZeRO-2和ZeRO-3的區別是什么?

大家好,我是皮先生!! 今天給大家分享一些關于大模型面試常見的面試題,希望對大家的面試有所幫助。 往期回顧: 大模型面經 | 春招、秋招算法面試常考八股文附答案(RAG專題一) 大模型面經 | 春招、秋招算法面試常考八股文附答案(RAG專題二) 大模型面經 | 春招、秋招算法…

spring boot 文件上傳

1.編寫文件上傳的表單頁面 <!DOCTYPE html> <html lang"en" xmlns:th"http://www.thymeleaf.org"> <head><meta charset"UTF-8"><meta http-equiv"Content-Type" content"text/html; charsetUTF-8&qu…