Linux線程同步實戰:多線程程序的同步與調度

個人主頁:chian-ocean

文章專欄-Linux

Linux線程同步實戰:多線程程序的同步與調度

    • 個人主頁:chian-ocean
    • 文章專欄-Linux
  • 前言:
  • 為什么要實現線程同步
    • 線程饑餓(Thread Starvation)
      • 示例:搶票問題
  • 條件變量
    • 條件變量的工作原理
        • 常用操作:
    • `pthread_cond_wait`
    • `pthread_cond_signal`
    • `pthread_cond_broadcast`
  • 基于條件變量的生產消費者模型(阻塞隊列)
    • 生產-消費者模型(也叫做游有界緩沖區)
      • 模型原理
      • 工作原理
      • 同步機制
    • 阻塞隊列
      • 關鍵點分析
      • 錯誤與改進:
    • 生產消費模型
  • 基于信號量的生產-消費者模型(環形隊列)
    • POSIX信號量
    • 模型原理
    • 工作原理
    • 同步機制
    • 環形隊列
      • 詳細分析:
        • 類成員變量:
        • 構造函數:
        • 析構函數:
        • `push()` 方法(生產者操作):
        • `pop()` 方法(消費者操作):
    • 主函數
      • 消費者線程函數 (`cosumer`)
      • 生產者線程函數 (`productor`)
      • 主函數 (`main`)

前言:

Linux 是一個多任務操作系統,它通過提供多種線程同步機制來幫助開發人員有效地管理線程之間的協作與沖突。正確的線程同步不僅能避免這些問題,還能提升程序的可靠性和性能。

在這里插入圖片描述

為什么要實現線程同步

線程饑餓(Thread Starvation)

線程饑餓是指在多線程程序中,某些線程因為無法獲取到所需的資源,長時間被阻塞,導致無法執行,甚至永遠無法執行。這種情況通常發生在低優先級線程無法獲得 CPU 時間,或者無法獲得必要的鎖資源時。線程饑餓會導致系統資源無法得到充分利用,程序的性能和響應性也會下降。

示例:搶票問題

  • 最初的搶票問題出現了讀寫數據不一致問題,我們通過加鎖解決了問題。
#include <iostream>
#include <unistd.h>
#include <pthread.h>using namespace std;// 定義常量 NUM 表示創建線程的數量
#define NUM  10// 初始化互斥鎖 mutex,用于保護共享資源 tickets
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;// 定義共享資源 tickets,用于模擬票的數量
int tickets = 1000;// 線程的工作函數,用于搶購票
void* SanpUpTichets(void* args)
{// 將當前線程與主線程分離,確保線程結束時可以自動回收資源pthread_detach(pthread_self());// 無限循環,模擬持續搶票while(true){// 加鎖,保護共享資源 ticketspthread_mutex_lock(&mutex);// 如果還有票if(tickets > 0){// 打印當前線程的信息和剩余票數cout << "pthread: " <<(int64_t) args << " tickets: " << tickets << endl;tickets--;  // 搶票,票數減一}else {// 如果票已經搶完,解鎖并跳出循環pthread_mutex_unlock(&mutex);break;}// 解鎖,允許其他線程訪問pthread_mutex_unlock(&mutex);// usleep(20);  // 如果需要模擬延遲,可以解開注釋}return nullptr;
}int main()
{// 創建 NUM 個線程,模擬多個線程同時搶購票for(int i = 0 ; i < NUM ; i++){pthread_t tid;void* n = (void*)i;  // 傳遞線程編號作為參數pthread_create(&tid, nullptr, SanpUpTichets, n); // 創建新線程并執行搶票函數}// 主線程休眠 10 秒,確保子線程有足夠時間搶票sleep(10);return 0;
}

代碼分析:

  • 共享資源: tickets 代表剩余票數,所有線程都會訪問并修改它。
  • 互斥鎖: pthread_mutex_t mutex 用于確保同一時刻只有一個線程能夠訪問和修改 tickets,避免數據沖突。
  • 多線程創建: 創建了 10 個線程,每個線程執行 SanpUpTichets 函數,嘗試減少票數。
  • 線程同步: 每個線程在修改 tickets 前加鎖,修改完后解鎖,確保線程安全。
  • 問題:但是仍然存在一個問題,就是饑餓問題(發現都是8號進程進行搶票環節)。

在這里插入圖片描述

條件變量

條件變量(Condition Variable)是多線程編程中的一種同步機制,用于在線程之間傳遞信號或同步操作。它允許一個線程等待某個條件成立,然后再繼續執行。條件變量通常與互斥鎖(mutex)一起使用,來保護共享資源和確保線程同步

條件變量的工作原理

條件變量允許線程在某些條件成立時進行“等待”操作。當某個條件不滿足時,線程會進入等待狀態,直到被另一個線程通知條件已經滿足并可以繼續執行。

常用操作:
  • 等待:一個線程可以調用 pthread_cond_wait 來等待某個條件成立。調用這個函數時,它會自動釋放與條件變量關聯的互斥鎖,并讓線程進入等待狀態,直到其他線程通過條件變量通知它。
  • 通知:當某個條件滿足時,線程可以調用 pthread_cond_signalpthread_cond_broadcast 來通知等待的線程。pthread_cond_signal 會喚醒一個等待的線程,而 pthread_cond_broadcast 會喚醒所有等待的線程。

pthread_cond_wait

該函數使得線程在滿足某個條件之前進入阻塞狀態。調用此函數時,線程將等待一個條件變量上的信號通知。調用此函數時會自動釋放與條件變量關聯的互斥鎖,并讓線程進入等待狀態,直到條件滿足或被其他線程通知喚醒。

int pthread_cond_wait(pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex);

參數

  • cond:這是要等待的條件變量。它是一個 pthread_cond_t 類型的結構體,用來表示條件變量。

  • mutex:與條件變量關聯的互斥鎖,它是一個 pthread_mutex_t 類型的結構體。此互斥鎖用于保護共享資源的訪問,確保只有一個線程可以修改條件變量。

pthread_cond_signal

目的:此函數用于喚醒一個正在等待條件變量 cond 的線程。

語法

int pthread_cond_signal(pthread_cond_t *cond);

參數

  • cond:指向條件變量的指針。

pthread_cond_broadcast

目的:此函數用于喚醒所有正在等待條件變量 cond 的線程。

語法

int pthread_cond_broadcast(pthread_cond_t *cond);

參數

  • cond:指向條件變量的指針

基于條件變量的生產消費者模型(阻塞隊列)

生產-消費者模型(也叫做游有界緩沖區)

兩個進程(線程)共享一塊公共的緩沖區,其中一個生產者,將數據放入緩沖區;另外一個是消費者,將數據從緩沖區取走(也可以把這個問題一般化為m個生產者和n個消費者)

模型原理

  1. 生產者(Producer):生成數據、任務或產品,并將它們放入一個共享緩沖區。
  2. 消費者(Consumer):從共享緩沖區中取出數據并進行處理。
  3. 緩沖區(Buffer):充當生產者和消費者之間的中介,通常是一個有限的隊列或數組。緩沖區容量有限,當緩沖區已滿時,生產者需要等待;當緩沖區為空時,消費者需要等待。

工作原理

  • 生產者:將生成的數據放入緩沖區。
  • 消費者:從緩沖區獲取數據并進行處理。
  • 緩沖區:充當共享資源,在多線程環境下進行同步。生產者和消費者的速度不同,可能會出現緩沖區滿或者為空的情況。

同步機制

在生產者-消費者模型中,通常需要使用同步機制來確保生產者和消費者之間不會發生沖突,常見的同步機制包括:

  • 互斥鎖(Mutex):確保每次只有一個線程能夠訪問共享資源。
  • 條件變量(Condition Variable):允許線程在特定條件下被掛起和喚醒,例如生產者等待緩沖區有空間時,消費者等待緩沖區有數據時。
  • 信號量(Semaphore):控制對共享資源的訪問數量,通常用于限制對緩沖區的訪問。

阻塞隊列

#include<iostream>
#include<queue>
#include<pthread.h>#define defaultnum 5 // 定義隊列的默認容量為5// 阻塞隊列模板類,支持泛型T(可以存儲任意類型的數據)
template<class T>
class blockqueue
{
private:std::queue<T> _q;              // 用于存儲數據的標準隊列int _cap = defaultnum;         // 隊列的最大容量,默認為5pthread_mutex_t _lock;         // 互斥鎖,用于保證線程安全pthread_cond_t _c_cond;        // 消費者等待的條件變量,當隊列為空時,消費者線程會等待pthread_cond_t _p_cond;        // 生產者等待的條件變量,當隊列滿時,生產者線程會等待
public:// 構造函數,接受隊列的最大容量blockqueue(int cap = defaultnum):_cap(cap){// 初始化互斥鎖和條件變量pthread_mutex_init(&_lock, nullptr);pthread_cond_init(&_c_cond, nullptr);pthread_cond_init(&_p_cond, nullptr);}// 析構函數,銷毀互斥鎖和條件變量~blockqueue(){pthread_mutex_destroy(&_lock);  // 銷毀互斥鎖pthread_cond_destroy(&_c_cond); // 銷毀消費者條件變量pthread_cond_destroy(&_p_cond); // 銷毀生產者條件變量}// 生產者線程調用的push方法,插入數據到隊列中void push(const T& in){pthread_mutex_lock(&_lock);  // 上鎖,確保線程安全// 如果隊列已滿,生產者需要等待if (_cap == _q.size()) {pthread_cond_wait(&_p_cond, &_lock); // 等待生產者條件變量}// 將數據放入隊列_q.push(in);// 喚醒一個消費者線程(如果有的話)pthread_cond_signal(&_c_cond);pthread_mutex_unlock(&_lock);  // 解鎖}// 消費者線程調用的pop方法,從隊列中取出數據T pop(){pthread_mutex_lock(&_lock);  // 上鎖,確保線程安全// 如果隊列為空,消費者需要等待while (_q.size() == 0) {pthread_cond_wait(&_c_cond, &_lock); // 等待消費者條件變量}// 從隊列中取出數據T data = _q.front();_q.pop();// 喚醒一個生產者線程(如果有的話)pthread_cond_signal(&_p_cond);pthread_mutex_unlock(&_lock);  // 解鎖return data;  // 返回取出的數據}
};

關鍵點分析

  1. 互斥鎖 (_lock):用于保護隊列的訪問,確保每次只有一個線程能訪問隊列。這樣可以避免多個線程同時修改隊列引發的數據競爭問題。
  2. 條件變量 (_c_cond_p_cond)
    • _c_cond 用于消費者線程,當隊列為空時,消費者會等待該條件變量。
    • _p_cond 用于生產者線程,當隊列滿時,生產者會等待該條件變量。
  3. 生產者操作 (push)
    • 如果隊列已滿,生產者會等待,直到有空位可以插入新的數據。
    • 插入數據后,通過 pthread_cond_signal 喚醒一個等待的消費者線程。
  4. 消費者操作 (pop)
    • 如果隊列為空,消費者會等待,直到有新數據可以消費。
    • 從隊列中取出數據后,通過 pthread_cond_signal 喚醒一個等待的生產者線程。

錯誤與改進:

  • 線程阻塞的合理性:此實現正確處理了生產者和消費者的等待機制,保證了生產者不會在隊列滿時繼續生產,消費者不會在隊列空時繼續消費。
  • pthread_cond_wait 調用條件:使用while而不是if來等待消費,因為在高并發環境下,可能會出現虛假喚醒(即線程被喚醒后,隊列可能仍為空或已滿)。

生產消費模型

#include<iostream>               // 引入標準輸入輸出庫
#include<unistd.h>               // 引入Unix標準庫,提供sleep函數等
#include "blockqueue.hpp"        // 引入blockqueue頭文件,定義了阻塞隊列
#include "task.hpp"              // 引入task頭文件,定義了任務類
using namespace std;void * producter(void* args)    // 生產者線程函數,傳入的參數為阻塞隊列的地址
{blockqueue<task>* bt = static_cast<blockqueue<task>*>(args);   // 將參數轉換為blockqueue類型while(true){int x = rand() % 10;   // 隨機生成一個整數x,范圍0到9int y = rand() % 10;   // 隨機生成一個整數y,范圍0到9task t(x, y, opers[rand() % 4]);  // 創建一個task對象,運算符從opers數組中隨機選擇t.run();  // 執行任務的run方法bt->push(t);  // 將生成的任務t放入阻塞隊列cout << "生產一個數據: " << t.Gettask() << endl;  // 輸出任務信息sleep(1);   // 休眠1秒,模擬生產的間隔時間}return nullptr;  // 返回空指針,結束線程函數
}void* consumer(void* args)  // 消費者線程函數,傳入的參數為阻塞隊列的地址
{blockqueue<task>* bt = static_cast<blockqueue<task>*>(args);  // 將參數轉換為blockqueue類型while(true){task data = bt->pop();  // 從阻塞隊列中取出一個任務cout << "消費一個數據: " << data.Getresult() << endl;  // 輸出任務結果}
}int main()
{blockqueue<task>* bq = new blockqueue<task>();  // 創建一個新的阻塞隊列對象bqsrand(time(nullptr));   // 使用當前時間作為隨機數種子,以確保每次運行的隨機數不同pthread_t ctid, ptid;  // 定義消費者線程和生產者線程的線程ID// 創建生產者線程,傳入bq作為參數pthread_create(&ptid, nullptr, producter, bq);  // 創建消費者線程,傳入bq作為參數pthread_create(&ctid, nullptr, consumer, bq);// 等待消費者線程結束pthread_join(ctid, nullptr);// 等待生產者線程結束pthread_join(ptid, nullptr);return 0;  // 主函數返回
}
  1. **生產者線程(producter):
  • 該線程負責生產任務并將其加入到阻塞隊列中。
  • 每次循環中,生產者隨機生成兩個整數 xy,并根據一個隨機的運算符構造一個 task 對象。
  • 調用 task 對象的 run 方法進行任務處理后,將任務推送到阻塞隊列中。
  • 輸出任務的相關信息,并休眠1秒,模擬生產任務的過程。
  1. 消費者線程(consumer
  • 該線程負責從阻塞隊列中獲取任務并消費(處理)這些任務。
  • 消費者通過 pop 方法從阻塞隊列中獲取任務,然后輸出任務的結果。
  1. 主函數(main
  • 創建一個 blockqueue<task> 類型的對象 bq,用于存放 task 對象。
  • 設置隨機數種子,以確保每次運行時生成的隨機數不同。
  • 創建并啟動生產者線程和消費者線程,傳遞 bq 作為參數。
  • 使用 pthread_join 等待生產者和消費者線程的結束。

基于信號量的生產-消費者模型(環形隊列)

POSIX信號量

  1. sem_init():初始化信號量。
  2. sem_wait():執行“等待”操作,降低信號量的值,若信號量為0,則進程會被阻塞。
  3. sem_post():執行“釋放”操作,增加信號量的值,若有進程等待該信號量,會喚醒一個進程。
  4. sem_destroy():銷毀信號量。

模型原理

組件描述
生產者(Producer)不斷生產數據,嘗試放入緩沖區
消費者(Consumer)不斷消費數據,嘗試從緩沖區取出
環形緩沖區(Circular Queue)共享的有限大小的隊列,生產者寫入,消費者讀取
信號量(Semaphore)控制生產者和消費者行為,保證同步與互斥

工作原理

環形隊列是一種先進先出(FIFO)的結構,具備“循環”特性:

  • 使用一個固定大小的數組 buffer[N]
  • 使用兩個指針:
    • head:消費者從這里取數據。
    • tail:生產者向這里放數據。
  • 通過模運算(% N)實現循環結構:
    tailhead 增加到數組尾部時,再次從頭開始。

同步機制

  1. empty_slots 信號量:表示環形隊列中空槽的數量。生產者每次生產一個產品時,減少一個空槽;如果空槽為0,生產者將被阻塞,直到消費者取走產品,釋放空槽。
  2. full_slots 信號量:表示環形隊列中已填充的槽的數量。消費者每次消費一個產品時,減少一個滿槽;如果滿槽為0,消費者將被阻塞,直到生產者生產產品并釋放滿槽。
  3. mutex 信號量:保證生產者和消費者對環形隊列的互斥訪問,防止多個線程同時修改隊列,導致數據沖突。

環形隊列

這段代碼實現了一個基于 信號量互斥鎖環形隊列(Ring Queue)Ringqueue,其中 T 是隊列元素的類型。環形隊列的大小是可配置的,并且支持生產者和消費者并發訪問。

讓我們逐行分析這段代碼并添加詳細注釋:

const static int defaultcap = 6; // 默認隊列大小為6template<class T> // 泛型隊列,支持任意類型的元素
class Ringqueue
{
private:std::vector<T> _ringqueue; // 存儲環形隊列元素int _cap;                  // 隊列容量int _c_step;               // 消費者隊列指針,表示下一個消費的位置int _p_step;               // 生產者隊列指針,表示下一個生產的位置// 信號量,用于同步生產者和消費者的操作sem_t _cdata_sem;  // 消費者信號量(數據),表示可供消費者消費的數據數量sem_t _pspace_sem;  // 生產者信號量(空間),表示可供生產者生產的空槽數量// 互斥鎖,用于保護生產者和消費者對隊列的訪問,防止競爭條件pthread_mutex_t _mutex_c; // 用于消費者線程的互斥鎖pthread_mutex_t _mutex_p; // 用于生產者線程的互斥鎖public:// 構造函數,初始化環形隊列、信號量和互斥鎖Ringqueue(int cap = defaultcap): _ringqueue(cap), _cap(cap), _c_step(0), _p_step(0) // 初始化環形隊列,容量和指針{sem_init(&_cdata_sem, 0, 0); // 初始化消費者信號量,初始為0,表示沒有數據可消費sem_init(&_pspace_sem, 0, cap); // 初始化生產者信號量,初始為隊列容量,表示有空間可以生產pthread_mutex_init(&_mutex_c, nullptr); // 初始化消費者互斥鎖pthread_mutex_init(&_mutex_p, nullptr); // 初始化生產者互斥鎖}// 析構函數,銷毀信號量和互斥鎖~Ringqueue(){sem_destroy(&_cdata_sem); // 銷毀消費者信號量sem_destroy(&_pspace_sem); // 銷毀生產者信號量pthread_mutex_destroy(&_mutex_c); // 銷毀消費者互斥鎖pthread_mutex_destroy(&_mutex_p); // 銷毀生產者互斥鎖}// 插入數據到隊列(生產者操作)void push(const T& in){// 等待空槽信號量(即空間是否足夠,如果沒有空槽,生產者阻塞)sem_wait(&_pspace_sem);// 獲取生產者互斥鎖,確保只有一個生產者可以修改隊列pthread_mutex_lock(&_mutex_p);// 將數據插入到隊列的生產者指針位置_ringqueue[_p_step++] = in;// 更新生產者指針(確保指針在環形隊列中循環)_p_step %= _cap;// 釋放生產者互斥鎖pthread_mutex_unlock(&_mutex_p);// 增加消費者信號量,表示隊列中有新數據可供消費sem_post(&_cdata_sem);}// 從隊列取數據(消費者操作)T pop(){// 等待數據信號量(即是否有數據,如果沒有數據,消費者阻塞)sem_wait(&_cdata_sem);// 獲取消費者互斥鎖,確保只有一個消費者可以修改隊列pthread_mutex_lock(&_mutex_c);// 從隊列的消費者指針位置取出數據T data = _ringqueue[_c_step++];// 更新消費者指針(確保指針在環形隊列中循環)_c_step %= _cap;// 釋放消費者互斥鎖pthread_mutex_unlock(&_mutex_c);// 增加生產者信號量,表示隊列中有空槽可以生產數據sem_post(&_pspace_sem);// 返回消費者取出的數據return data;}
};

詳細分析:

類成員變量:
  1. _ringqueue:這是一個 std::vector<T>,用于存儲環形隊列的數據。
  2. _cap:隊列的容量,表示隊列的最大長度。
  3. _c_step_p_step:分別是消費者和生產者指針,用于指示隊列中下一個被訪問的元素的位置。它們是環形隊列的重要組成部分,通過模運算實現循環。
  4. _cdata_sem_pspace_sem:消費者和生產者的信號量,用于同步生產者和消費者的操作,確保生產者不會在隊列滿時插入數據,消費者不會在隊列空時取出數據。
  5. _mutex_c_mutex_p:用于保護消費者和生產者線程訪問隊列的互斥鎖,防止并發線程訪問隊列時發生數據競爭。
構造函數:
  • 初始化隊列大小、信號量和互斥鎖。
  • sem_init(&_cdata_sem, 0, 0) 將消費者信號量初始為0,表示隊列中沒有數據可供消費。
  • sem_init(&_pspace_sem, 0, cap) 將生產者信號量初始為隊列的容量,表示有足夠的空間可以進行生產。
析構函數:
  • 銷毀信號量和互斥鎖,釋放資源。
push() 方法(生產者操作):
  1. sem_wait(&_pspace_sem):首先,生產者通過等待 pspace_sem 信號量來檢查是否有空槽可以插入數據。如果沒有空槽(即隊列滿),生產者線程將阻塞,直到有空槽可用。
  2. pthread_mutex_lock(&_mutex_p):然后,生產者獲取生產者的互斥鎖,確保在插入數據時不會有其他線程同時訪問隊列。
  3. 插入數據:生產者將數據插入隊列,并更新 p_step 指針。
  4. pthread_mutex_unlock(&_mutex_p):釋放互斥鎖,允許其他線程訪問隊列。
  5. sem_post(&_cdata_sem):最后,生產者通過增加 cdata_sem 信號量來通知消費者隊列中有新的數據可供消費。
pop() 方法(消費者操作):
  1. sem_wait(&_cdata_sem):消費者通過等待 cdata_sem 信號量來檢查隊列是否有數據可以消費。如果沒有數據(即隊列空),消費者線程將阻塞,直到有數據可用。
  2. pthread_mutex_lock(&_mutex_c):然后,消費者獲取消費者的互斥鎖,確保在取出數據時不會有其他線程同時訪問隊列。
  3. 取出數據:消費者從隊列中取出數據,并更新 c_step 指針。
  4. pthread_mutex_unlock(&_mutex_c):釋放互斥鎖,允許其他線程訪問隊列。
  5. sem_post(&_pspace_sem):最后,消費者通過增加 pspace_sem 信號量來通知生產者隊列中有空槽可以插入數據。

這段代碼展示了一個簡單的生產者-消費者模型,其中使用了環形隊列Ringqueue)來存儲任務(task)。生產者線程生成任務并將其放入隊列,消費者線程從隊列中取出任務并處理。以下是代碼的詳細分析和注釋:

主函數

消費者線程函數 (cosumer)

void* cosumer(void* args)
{Ringqueue<task>* rq = static_cast<Ringqueue<task>*>(args);while (true){task data = rq->pop();cout <<"消費了一個數據: " << data.Getresult() <<endl;}return nullptr;
}
  • cosumer 是消費者線程的入口函數。它會一直從隊列中取出任務并進行處理。
  • 使用 pop() 方法從隊列中取出一個 task 對象。
  • Getresult() 假設是 task 類中的方法,返回任務的結果。這里通過輸出該結果來模擬消費操作。
  • while (true) 使得消費者線程持續運行,直到程序結束。

生產者線程函數 (productor)

void* productor(void* args)
{Ringqueue<task>* rq = static_cast<Ringqueue<task>*>(args);while (true){int x = rand() % 10;int y = rand() % 10;task t(x, y, opers[rand() % opers.size()]);sleep(1);t.run();rq->push(t);cout << "生產一個數據:" << t.Gettask() << endl;usleep(1);}return nullptr;
}
  • productor 是生產者線程的入口函數。它持續生成任務并將其插入到環形隊列中。
  • rand() % 10 生成兩個隨機數 xy,這兩個數作為任務的操作數。
  • task t(x, y, opers[rand() % opers.size()]) 創建一個新的任務對象,假設 opers 是一個操作符數組(例如,加法、減法等),從中隨機選擇一個操作符。
  • sleep(1) 模擬任務的生成過程,表示生產一個任務需要1秒鐘時間。
  • t.run() 執行任務(假設 run() 方法執行任務操作)。
  • rq->push(t) 將任務推送到隊列中。
  • cout 用于輸出生產的任務信息。
  • usleep(1) 將線程掛起1微秒,減少CPU的占用。

主函數 (main)

int main()
{srand(time(nullptr));  // 用當前時間作為隨機數種子,確保每次運行時生成不同的隨機數pthread_t ctid, pptid, ptid;  // 聲明線程IDRingqueue<task>* rq = new Ringqueue<task>();  // 創建一個環形隊列對象,用于存儲任務// 創建消費者線程pthread_create(&ctid, nullptr, cosumer, rq);// 創建兩個生產者線程pthread_create(&ptid, nullptr, productor, rq);pthread_create(&pptid, nullptr, productor, rq);// 等待所有線程完成pthread_join(ctid, nullptr);pthread_join(ptid, nullptr);pthread_join(pptid, nullptr);return 0;
}
  • srand(time(nullptr)):使用當前時間作為隨機數種子,確保每次程序運行時生成不同的隨機數序列。
  • pthread_t ctid, pptid, ptid:聲明三個線程ID變量,分別用于消費者線程和兩個生產者線程。
  • Ringqueue<task>* rq = new Ringqueue<task>();:創建一個環形隊列對象,用于存儲生產者和消費者之間傳遞的任務。
  • pthread_create(&ctid, nullptr, cosumer, rq);:創建消費者線程,傳入環形隊列指針 rq
  • pthread_create(&ptid, nullptr, productor, rq);pthread_create(&pptid, nullptr, productor, rq);:創建兩個生產者線程,傳入同一個環形隊列指針 rq
  • pthread_join 用于等待線程執行完畢。由于 pthread_create 是異步執行的,所以在主線程中使用 pthread_join 等待每個線程的結束,確保程

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

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

相關文章

5.2 初識Spark Streaming

在本節實戰中&#xff0c;我們初步探索了Spark Streaming&#xff0c;它是Spark的流式數據處理子框架&#xff0c;具備高吞吐量、可伸縮性和強容錯能力。我們了解了Spark Streaming的基本概念和運行原理&#xff0c;并通過兩個案例演示了如何利用Spark Streaming實現詞頻統計。…

Go 即時通訊系統:日志模塊重構,并從main函數開始

重構logger 上次寫的logger.go過于繁瑣&#xff0c;有很多沒用到的功能&#xff1b;重構后只提供了簡潔的日志接口&#xff0c;支持日志輪轉、多級別日志記錄等功能&#xff0c;并采用單例模式確保全局只有一個日志實例 全局變量 var (once sync.Once // 用于實現…

「數據采集與網絡爬蟲(使用Python工具)」【數據分析全棧攻略:爬蟲+處理+可視化+報告】

- 第 103 篇 - Date: 2025 - 06 - 01 Author: 鄭龍浩/仟墨 文章目錄 「據采集與網絡爬蟲」【使用工具&#xff1a;Python】一 數據采集1 數據采集綜述&#xff08;1&#xff09;基本介紹&#xff08;2&#xff09;數據目標源&#xff08;3&#xff09;采集方式&#xff08;4&am…

響應式系統與Spring Boot響應式應用開發

響應式系統概述 過去十年間,為應對移動和云計算的需求,軟件行業通過改進開發流程來構建更穩定、健壯且靈活的軟件系統。這種演進不僅服務于傳統用戶端(桌面/Web),還需支持多樣化設備(手機、傳感器等)。為應對這些挑戰,多個組織共同制定了《響應式宣言》(2014年發布)…

POJO、DTO和VO:Java應用中的三種關鍵對象詳解

在軟件開發特別是Java開發中&#xff0c;常常會遇到POJO、DTO和VO這三類對象。它們在不同場景下扮演著重要角色&#xff0c;有助于優化代碼結構、增強系統安全性和提升性能。本文將全面解析這三者的定義、區別及常見使用場景&#xff0c;幫助你更好地理解和應用。 1. POJO&…

leetcode付費題 353. 貪吃蛇游戲解題思路

貪吃蛇游戲試玩:https://patorjk.com/games/snake/ 問題描述 設計一個貪吃蛇游戲,要求實現以下功能: 初始化游戲:給定網格寬度、高度和食物位置序列移動操作:根據指令(上、下、左、右)移動蛇頭規則: 蛇頭碰到邊界或自身身體時游戲結束(返回-1)吃到食物時蛇身長度增加…

NLP學習路線圖(十三):正則表達式

在自然語言處理&#xff08;NLP&#xff09;的浩瀚宇宙中&#xff0c;原始文本數據如同未經雕琢的璞玉。而文本預處理&#xff0c;尤其是其中至關重要的正則表達式技術&#xff0c;正是將這塊璞玉轉化為精美玉器的核心工具集。本文將深入探討正則表達式在NLP文本預處理中的原理…

計算機網絡(4)——網絡層

1.概述 1.1 網絡層服務 (1) 網絡層為不同主機(Host)之間提供了一種邏輯通信機制 (2)每個主機和路由器都運行網絡層協議 發送方&#xff1a;將來自傳輸層的消息封裝到數據報(datagram)中接收方&#xff1a;向傳輸層交付數據段(segment) 1.2 網絡層核心功能 路由選擇(routing…

EMO2:基于末端執行器引導的音頻驅動虛擬形象視頻生成

今天帶來EMO2&#xff08;全稱End-Effector Guided Audio-Driven Avatar Video Generation&#xff09;是阿里巴巴智能計算研究院研發的創新型音頻驅動視頻生成技術。該技術通過結合音頻輸入和靜態人像照片&#xff0c;生成高度逼真且富有表現力的動態視頻內容&#xff0c;值得…

[Redis] Redis:高性能內存數據庫與分布式架構設計

標題&#xff1a;[Redis] 淺談分布式系統 水墨不寫bug 文章目錄 一、什么是Redis&#xff1f;一、核心定位二、核心優勢三、典型應用場景四、Redis vs 傳統數據庫 二、架構選擇與設計1、單機架構&#xff08;應用程序 數據庫服務器&#xff09;2、應用程序和數據庫服務器分離3…

HTML5 視頻播放器:從基礎到進階的實現指南

在現代Web開發中&#xff0c;視頻播放功能是許多網站的重要組成部分。無論是在線教育平臺、視頻分享網站&#xff0c;還是企業官網&#xff0c;HTML5視頻播放器都扮演著不可或缺的角色。本文將從基礎到進階&#xff0c;詳細介紹如何實現一個功能完善的HTML5視頻播放器&#xff…

牛客小白月賽117

前言&#xff1a;solveABCF相對簡單&#xff0c;D題思路簡單但是實現麻煩&#xff0c;F題郭老師神力b(&#xffe3;▽&#xffe3;)。 A. 好字符串 題目大意&#xff1a;給定字符串s&#xff0c;里面的字母必須大小寫同時出現。 【解題】&#xff1a;沒什么好說的&#xff0…

特倫斯 S75 電鋼琴:重構演奏美學的極致表達

在數字音樂時代&#xff0c;電鋼琴正從功能性樂器升級為融合藝術、科技與生活的美學載體。特倫斯 S75 電鋼琴以極簡主義哲學重構產品設計&#xff0c;將專業級演奏體驗與現代家居美學深度融合&#xff0c;為音樂愛好者打造跨越技術邊界的沉浸式藝術空間。 一、極簡主義的視覺敘…

GpuGeek 618大促引爆AI開發新體驗

隨著生成式AI技術迅猛發展&#xff0c;高效可靠的算力資源已成為企業和開發者突破創新瓶頸的戰略支點。根據賽迪顧問最新發布的《2025中國AI Infra平臺市場發展研究報告》顯示&#xff0c;2025年中國生成式人工智能企業應用市場規模將達到629.0億元&#xff0c;作為AI企業級應用…

第二十章 文本處理

第二十章 文本處理 所有類UNIX系統都嚴重依賴于文本文件來存儲數據&#xff0c;所以存在大量文本操作工具也在情理之中。 相關命令: cat&#xff1a;拼接文件。sort&#xff1a;排序文本行。uniq&#xff1a;報告或忽略重復的行。cut&#xff1a;從每行中刪除部分內容。past…

Reactor 和 Preactor

Reactor 和 Preactor 是兩個在工業控制、生產調度和事件驅動系統中非常重要的設計模式或框架&#xff0c;不少人會用這兩個名詞來描述不同的編程思想或技術架構。 一、Reactor 模式&#xff08;反應器模式&#xff09; 1. 概述 Reactor 模式其實是一種I/O事件通知的設計思想…

siglip2(2) Naflex模型的動態分辨率原理

動態分辨率的圖片縮放行為 操作辦法: 操作1。修改preprocessor_config.json,設置"max_num_patches": 256,可從256(1616)改為196(1414)。 操作2。在預處理圖片時,可按照如下方式傳入參數max_num_patches。 inputs = self.processor(images=videos, **{"ima…

??技術深度解析:《鴻蒙5.0+:無感續航的智能魔法》?

??引言&#xff1a;從“充電焦慮”到“無感續航”?? ??用戶痛點??&#xff1a; 刷短視頻時電量暴跌、夜間待機掉電快、多設備切換耗電失控——傳統系統無法平衡性能與功耗。??鴻蒙5.0突破??&#xff1a; 通過??方舟引擎3.0??&#xff08;編譯級能效優化&#…

振動力學的三類基本問題

振動問題的分類依賴于分類的出發點&#xff0c;本文從系統論的角度來分析振動問題的分類。如圖1&#xff0c;一個振動系統&#xff0c;包括三個方面&#xff1a;輸入、系統特性&#xff08;或稱為系統模型&#xff09;、輸出。其中&#xff0c;輸入指外界載荷&#xff0c;包括力…

過濾攻擊-聚合數據

公開的聚合數據是通過對原始細粒度數據進行匯總、統計或轉換后發布的&#xff0c;旨在提供群體層面的洞察而非個體信息。它們具有以下關鍵特征&#xff1a; 1. 去標識性&#xff08;De-identification&#xff09; 表現&#xff1a; 直接標識符&#xff08;姓名、身份證號、手機…