【C++ 语言】线程安全队列 ( 条件变量 | 线程调度 )(二)

简介: 【C++ 语言】线程安全队列 ( 条件变量 | 线程调度 )(二)

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 中的 notifyAll
  pthread_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: 如有需要,请添加测试并安装目标。


运行结果


image.png




V . 示例代码说明


下载完项目后 , 使用 Visual Studio 打开 , 注意需要配置 POSIX 线程库 ;


【Visual Studio】Visual Studio 2019 社区版 CMakeList 开发环境安装 ( 下载 | 安装相关组件 | 创建编译执行项目 | 错误处理 )


【Visual Studio 2019】创建 导入 CMake 项目


【C++ 语言】Visual Studio 配置 POSIX 线程 ( Windows 不支持 POSIX | 配置文件下载 | 库文件说明 | 配置过程 )


目录
相关文章
|
11天前
|
存储 安全 算法
Go语言是如何支持多线程的
【10月更文挑战第21】Go语言是如何支持多线程的
104 72
|
4天前
|
缓存 安全 C++
C++无锁队列:解锁多线程编程新境界
【10月更文挑战第27天】
19 7
|
4天前
|
消息中间件 存储 安全
|
11天前
|
Go 调度 开发者
Go语言多线程的优势
【10月更文挑战第15天】
11 4
|
21天前
|
算法 C++
2022年第十三届蓝桥杯大赛C/C++语言B组省赛题解
2022年第十三届蓝桥杯大赛C/C++语言B组省赛题解
22 5
|
1月前
|
存储 运维 API
源码解密协程队列和线程队列的实现原理(一)
源码解密协程队列和线程队列的实现原理(一)
33 1
|
1月前
|
存储 安全 API
源码解密协程队列和线程队列的实现原理(二)
源码解密协程队列和线程队列的实现原理(二)
30 1
|
21天前
|
安全 调度 C#
STA模型、同步上下文和多线程、异步调度
【10月更文挑战第19天】本文介绍了 STA 模型、同步上下文和多线程、异步调度的概念及其优缺点。STA 模型适用于单线程环境,确保资源访问的顺序性;同步上下文和多线程提高了程序的并发性和响应性,但增加了复杂性;异步调度提升了程序的响应性和资源利用率,但也带来了编程复杂性和错误处理的挑战。选择合适的模型需根据具体应用场景和需求进行权衡。
|
25天前
|
存储 编译器 C语言
深入计算机语言之C++:类与对象(上)
深入计算机语言之C++:类与对象(上)
|
25天前
|
存储 分布式计算 编译器
深入计算机语言之C++:C到C++的过度-2
深入计算机语言之C++:C到C++的过度-2