【C++ 语言】线程安全队列 ( 条件变量 | 线程调度 )
文章目錄
- I . 線程簡單使用
- II . 互斥鎖
- III . 條件變量 線程同步
- IV . 完整代碼示例
- 006_ThreadSafeQueue.h
- 006_ThreadSafeQueue.cpp
- SafeQueue.h
- CMakeLists.txt
- 運行結果
- V . 示例代碼說明
I . 線程簡單使用
線程簡單使用流程 :
① 線程方法準備 : 定義一個方法 , 主要使用其 方法名稱 和 返回值 ;
//線程的主方法 , 類似于 Java 中的 run 方法 , C++ 中方法名隨意 void* pushData(void*) {// ... }② 聲明線程 ID : 線程 ID 類型是 pthread_t 類型的 , 其本質是 int 類型 ;
pthread_t pid_push;③ 創建線程并執行 : pthread_create() 方法時創建并啟動線程 ;
//啟動一個線程 , 無限循環 向線程安全隊列中存儲數據 pthread_create(&pid_push, 0, pushData, 0);該方法需要提供四個參數 :
- 參數 1 ( pthread_t *tidp ) :線程標識符指針 , 該指針指向線程標識符 ;
- 參數 2 ( const pthread_attr_t *attr ) : 線程屬性指針 ;
- 參數 3 ( (void*)(*start_rtn)(void*) ) : 線程運行函數指針 , start_rtn 是一個函數指針 , 其參數和返回值類型是 void* 類型
- 參數 4 ( void *arg ) : 參數 3 中的線程運行函數的參數 ;
④ 等待線程執行完畢 : pthread_join (pthread_t thread, void **value_ptr)方法 , 等待 thread 線程 ID 代表的線程執行完畢 ;
//阻塞 , 等待其中任意一個線程執行完畢 , 實際上是一直在此阻塞 , 如果運行下去 主函數就暫停了 pthread_join(pid_push, 0);更多詳細內容 ( 如線程屬性設置等細節 ) 參考 下面的博客 :
【C++ 語言】線程 ( 線程創建方法 | 線程標識符 | 線程屬性 | 線程屬性初始化 | 線程屬性銷毀 | 分離線程 | 線程調度策略 | 線程優先級 | 線程等待 )
【C++ 語言】Visual Studio 配置 POSIX 線程 ( Windows 不支持 POSIX | 配置文件下載 | 庫文件說明 | 配置過程 )
II . 互斥鎖
互斥鎖使用流程 :
① 聲明互斥鎖變量 :
//互斥鎖變量 // 1. 先導入頭文件 // 2. 定義互斥鎖變量 // 3. 在構造函數中進行初始化 // 4. 在析構函數中釋放 pthread_mutex_t mutex;② 初始化互斥鎖 :
//初始化互斥鎖 pthread_mutex_init(&mutex, 0);③ 上鎖 :
//使用互斥鎖將操作鎖起來 pthread_mutex_lock(&mutex);④ 互斥操作 : 需要進行互斥的操作 , 放在 上鎖 與 解鎖之間進行 ;
⑤ 解鎖 :
//解除互斥鎖 鎖定 pthread_mutex_unlock(&mutex);⑥ 銷毀互斥鎖 : 互斥鎖使用完畢后進行銷毀 ;
//釋放互斥鎖 pthread_mutex_destroy(&mutex);III . 條件變量 線程同步
條件變量使用步驟 :
① 聲明 條件變量 :
//條件變量 // 使用流程 : // 1. 在構造函數中進行初始化 // 2. 在析構函數中釋放 pthread_cond_t cond;② 初始化 條件變量 : 一般在構造函數中執行 ;
//初始化條件變量 pthread_cond_init(&cond, 0);③ 阻塞線程 :
//阻塞等待 , 相當于 Java 中的 wait() 方法 pthread_cond_wait(&cond, &mutex);④ 解除線程阻塞 : 有兩種方式 , 前者每次只能喚醒一個線程 , 并且無法確定喚醒哪個線程 ; 后者喚醒所有由 cond 條件變量阻塞的線程 ;
//方式 1 : 喚醒一個線程 , 喚醒哪個線程 是無法控制的 ; 該方法 相當于 Java 中的 notify() pthread_cond_signal(&cond);//方式 2 : 使用廣播通知所有等待的線程 , 喚醒所有的線程 , 相當于 Java 中的 notifyAll pthread_cond_broadcast(&cond);⑤ 銷毀 條件變量 : 一般在析構函數中進行 ;
//銷毀條件變量 pthread_cond_destroy(&cond);IV . 完整代碼示例
006_ThreadSafeQueue.h
// 006_ThreadSafeQueue.h: 標準系統包含文件的包含文件 // 或項目特定的包含文件。#pragma once#include <iostream>// TODO: 在此處引用程序需要的其他標頭。
006_ThreadSafeQueue.cpp
// 005_Thread.cpp: 定義應用程序的入口點。 //#include "006_ThreadSafeQueue.h" #include <pthread.h>//引入隊列的頭文件 #include <queue>//引入安全隊列頭文件 #include "SafeQueue.h"using namespace std;//線程安全隊列 SafeQueue<int> safeQueue;//向線程安全隊列中添加數據 void* pushData(void*) {//循環放入數據while (true){int i;//用戶從命令行輸入數據 , 將該數據 push 到線程安全隊列中cin >> i;safeQueue.push(i);cout << "存儲數據到線程安全隊列 : " << i << endl;}return 0; }//從線程安全隊列中取出數據 void* popData(void*) {//循環取出數據while (true){//無限獲取數據, 如果線程安全隊列中沒有數據, 就會在這里阻塞 , 直到 push 進一個數據 , 解除阻塞int i = 0;//注意傳入的是引用 , 可以直接給 i 賦值 , 當做返回值safeQueue.popAnyway(i);cout << "從線程安全隊列中取出出具 : " << i << "\n" << endl;}return 0; }/*測試 線程安全隊列 */ int main() {//兩個線程 , 一個 push 數據 ( 生產 ) , 一個 pop 數據 ( 消費 )pthread_t pid_push, pid_pop;//啟動一個線程 , 無限循環 向線程安全隊列中存儲數據pthread_create(&pid_push, 0, pushData, 0);//啟動一個線程 , 無限循環 向線程安全隊列中取出數據pthread_create(&pid_pop, 0, popData, 0);//阻塞 , 等待其中任意一個線程執行完畢 , 實際上是一直在此阻塞 , 如果運行下去 主函數就暫停了pthread_join(pid_push, 0);system("pause");return 0; }
SafeQueue.h
//避免被多次 include #pragma once//避免頭文件被多次包含 , 有兩種處理方式 // ① 一種是 #ifndef A #define A #endif 方式 // ② 另一種就是 使用 #pragma once 宏#include <queue>//引入頭文件 , 需要使用互斥鎖相關邏輯 #include <pthread.h>using namespace std;//創建一個模板類 , 對 Queue 進行封裝 , // 保證該 queue 隊列是一個線程安全的隊列 // 對 queue 隊列操作是線程安全的 template <typename T> class SafeQueue {public ://定義構造函數SafeQueue() {//初始化互斥鎖pthread_mutex_init(&mutex, 0);//初始化條件變量pthread_cond_init(&cond, 0);}//定義析構函數~SafeQueue() {//釋放互斥鎖pthread_mutex_destroy(&mutex);//銷毀條件變量pthread_cond_destroy(&cond);}//向隊列中加入元素 , 或 從隊列中取出元素// queue 隊列不是線程安全的 , 現在要保證該 queue 存儲元素是線程安全的// 需要使用互斥鎖控制 push ( 加入元素 ) 和 pop ( 取出元素 ) 操作 ; //向隊列中加入元素void push(T t) {//使用互斥鎖將操作鎖起來pthread_mutex_lock(&mutex);//使用互斥鎖 , 向隊列中加入數據是安全的safe_queue.push(t);//喚醒一個線程 , 喚醒哪個線程 是無法控制的 ; 該方法 相當于 Java 中的 notify() //pthread_cond_signal(&cond);//使用廣播通知所有等待的線程 , 喚醒所有的線程 , 相當于 Java 中的 notifyAllpthread_cond_broadcast(&cond);//解除互斥鎖pthread_mutex_unlock(&mutex);}/*現在要實現這樣一個需求 : 如果 pop 方法獲取時 , 該隊列 q 為空 , 此時肯定獲取不到數據了但是我們規定每次調用 pop 必須獲取一個數據這樣的話 , 如果檢測到 pop 中沒有數據 , 就必須先將線程阻塞等到有新的元素 push 進來后 , 解除阻塞 , 使用條件變量實現*///從隊列中取出元素 ( 無論如何都要獲取到 , 如果獲取不到就阻塞到能獲取到的時候 )void popAnyway(T& t) {//使用互斥鎖將操作鎖起來pthread_mutex_lock(&mutex);//如果沒有數據 , 那么阻塞等待數據 if (safe_queue.empty()) {//阻塞等待 , 相當于 Java 中的 wait() 方法pthread_cond_wait(&cond, &mutex);}//如果阻塞解除 , 那么執行下面的內容//t 參數是傳入的引用 , 這里可以直接給 t 引用賦值 t = safe_queue.front();//將首元素移除safe_queue.pop();//解除互斥鎖pthread_mutex_unlock(&mutex);}//從隊列中取出元素 ( 取數據時要判空 )void pop(T& t) {//使用互斥鎖將操作鎖起來pthread_mutex_lock(&mutex);//使用互斥鎖 , 向隊列中加入數據是安全的 , 如果隊列是空的 , 就獲取不到元素if (!safe_queue.empty()) {//t 參數是傳入的引用 , 這里可以直接給 t 引用賦值 t = safe_queue.front();//將首元素移除safe_queue.pop();}//解除互斥鎖pthread_mutex_unlock(&mutex);}private ://實際操作的隊列 ( 先進先出 ) , 該隊列不是線程安全的// 如果要保證該 Queue 是線程安全的話 , 就需要為其設置一個互斥鎖// 下面的 mutex 互斥鎖變量 , 就是為了保證該隊列是線程安全隊列而設置的queue<T> safe_queue;//互斥鎖變量 // 1. 先導入頭文件// 2. 定義互斥鎖變量// 3. 在構造函數中進行初始化// 4. 在析構函數中釋放pthread_mutex_t mutex;//條件變量// 使用流程 : // 1. 在構造函數中進行初始化// 2. 在析構函數中釋放pthread_cond_t cond;};
CMakeLists.txt
# CMakeList.txt: 005_Thread 的 CMake 項目,在此處包括源代碼并定義 # 項目特定的邏輯。 # cmake_minimum_required (VERSION 3.8)#引入頭文件 include_directories("include")#配置自動根據當前是 32 位還是 64 位程序 , 確定靜態庫的配置目錄 if(CMAKE_CL_64)set(platform x64) else()set(platform x86) endif() #配置靜態庫 , 用于引導如何鏈接動態庫和靜態庫 link_directories("lib/${platform}")#處理 “timespec”:“struct” 類型重定義 報錯信息 set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DHAVE_STRUCT_TIMESPEC")# 將源代碼添加到此項目的可執行文件。 add_executable (006_ThreadSafeQueue "006_ThreadSafeQueue.cpp" "006_ThreadSafeQueue.h")#鏈接生成的 006_ThreadSafeQueue 和線程動態庫名字 # 動態庫是 lib/x64 下的 pthreadVC2.lib target_link_libraries(006_ThreadSafeQueue pthreadVC2)# TODO: 如有需要,請添加測試并安裝目標。
運行結果
V . 示例代碼說明
下載完項目后 , 使用 Visual Studio 打開 , 注意需要配置 POSIX 線程庫 ;
【Visual Studio】Visual Studio 2019 社區版 CMakeList 開發環境安裝 ( 下載 | 安裝相關組件 | 創建編譯執行項目 | 錯誤處理 )
【Visual Studio 2019】創建 導入 CMake 項目
【C++ 語言】Visual Studio 配置 POSIX 線程 ( Windows 不支持 POSIX | 配置文件下載 | 庫文件說明 | 配置過程 )
總結
以上是生活随笔為你收集整理的【C++ 语言】线程安全队列 ( 条件变量 | 线程调度 )的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 【Android 应用开发】Androi
- 下一篇: 【Android 应用开发】Androi