【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 | 配置文件下载 | 库文件说明 | 配置过程 )


目录
相关文章
|
3天前
|
存储 负载均衡 算法
基于 C++ 语言的迪杰斯特拉算法在局域网计算机管理中的应用剖析
在局域网计算机管理中,迪杰斯特拉算法用于优化网络路径、分配资源和定位故障节点,确保高效稳定的网络环境。该算法通过计算最短路径,提升数据传输速率与稳定性,实现负载均衡并快速排除故障。C++代码示例展示了其在网络模拟中的应用,为企业信息化建设提供有力支持。
35 15
|
13天前
|
存储 算法 C++
【c++丨STL】priority_queue(优先级队列)的使用与模拟实现
本文介绍了STL中的容器适配器`priority_queue`(优先级队列)。`priority_queue`根据严格的弱排序标准设计,确保其第一个元素始终是最大元素。它底层使用堆结构实现,支持大堆和小堆,默认为大堆。常用操作包括构造函数、`empty`、`size`、`top`、`push`、`pop`和`swap`等。我们还模拟实现了`priority_queue`,通过仿函数控制堆的类型,并调用封装容器的接口实现功能。最后,感谢大家的支持与关注。
52 1
|
2月前
|
存储 C语言 C++
【C++数据结构——栈与队列】顺序栈的基本运算(头歌实践教学平台习题)【合集】
本关任务:编写一个程序实现顺序栈的基本运算。开始你的任务吧,祝你成功!​ 相关知识 初始化栈 销毁栈 判断栈是否为空 进栈 出栈 取栈顶元素 1.初始化栈 概念:初始化栈是为栈的使用做准备,包括分配内存空间(如果是动态分配)和设置栈的初始状态。栈有顺序栈和链式栈两种常见形式。对于顺序栈,通常需要定义一个数组来存储栈元素,并设置一个变量来记录栈顶位置;对于链式栈,需要定义节点结构,包含数据域和指针域,同时初始化栈顶指针。 示例(顺序栈): 以下是一个简单的顺序栈初始化示例,假设用C语言实现,栈中存储
160 77
|
2月前
|
C++
【C++数据结构——栈和队列】括号配对(头歌实践教学平台习题)【合集】
【数据结构——栈和队列】括号配对(头歌实践教学平台习题)【合集】(1)遇到左括号:进栈Push()(2)遇到右括号:若栈顶元素为左括号,则出栈Pop();否则返回false。(3)当遍历表达式结束,且栈为空时,则返回true,否则返回false。本关任务:编写一个程序利用栈判断左、右圆括号是否配对。为了完成本关任务,你需要掌握:栈对括号的处理。(1)遇到左括号:进栈Push()开始你的任务吧,祝你成功!测试输入:(()))
48 7
|
2月前
|
存储 C++ 索引
【C++数据结构——栈与队列】环形队列的基本运算(头歌实践教学平台习题)【合集】
【数据结构——栈与队列】环形队列的基本运算(头歌实践教学平台习题)【合集】初始化队列、销毁队列、判断队列是否为空、进队列、出队列等。本关任务:编写一个程序实现环形队列的基本运算。(6)出队列序列:yzopq2*(5)依次进队列元素:opq2*(6)出队列序列:bcdef。(2)依次进队列元素:abc。(5)依次进队列元素:def。(2)依次进队列元素:xyz。开始你的任务吧,祝你成功!(4)出队一个元素a。(4)出队一个元素x。
53 13
【C++数据结构——栈与队列】环形队列的基本运算(头歌实践教学平台习题)【合集】
|
2月前
|
存储 C语言 C++
【C++数据结构——栈与队列】链栈的基本运算(头歌实践教学平台习题)【合集】
本关任务:编写一个程序实现链栈的基本运算。开始你的任务吧,祝你成功!​ 相关知识 初始化栈 销毁栈 判断栈是否为空 进栈 出栈 取栈顶元素 初始化栈 概念:初始化栈是为栈的使用做准备,包括分配内存空间(如果是动态分配)和设置栈的初始状态。栈有顺序栈和链式栈两种常见形式。对于顺序栈,通常需要定义一个数组来存储栈元素,并设置一个变量来记录栈顶位置;对于链式栈,需要定义节点结构,包含数据域和指针域,同时初始化栈顶指针。 示例(顺序栈): 以下是一个简单的顺序栈初始化示例,假设用C语言实现,栈中存储整数,最大
52 9
|
2月前
|
存储 监控 Java
JAVA线程池有哪些队列? 以及它们的适用场景案例
不同的线程池队列有着各自的特点和适用场景,在实际使用线程池时,需要根据具体的业务需求、系统资源状况以及对任务执行顺序、响应时间等方面的要求,合理选择相应的队列来构建线程池,以实现高效的任务处理。
141 12
|
3月前
|
安全 Java 容器
【JaveEE】——多线程中使用顺序表,队列,哈希表
多线程环境下使用ArrayList(同步机制,写时拷贝),使用队列,哈希表(高频)ConcurrentHashMap(缩小锁粒度,CAS,扩容优化)
|
4月前
|
存储 设计模式 C++
【C++】优先级队列(容器适配器)
本文介绍了C++ STL中的线性容器及其适配器,包括栈、队列和优先队列的设计与实现。详细解析了`deque`的特点和存储结构,以及如何利用`deque`实现栈、队列和优先队列。通过自定义命名空间和类模板,展示了如何模拟实现这些容器适配器,重点讲解了优先队列的内部机制,如堆的构建与维护方法。
71 0
|
1月前
|
Linux
Linux编程: 在业务线程中注册和处理Linux信号
本文详细介绍了如何在Linux中通过在业务线程中注册和处理信号。我们讨论了信号的基本概念,并通过完整的代码示例展示了在业务线程中注册和处理信号的方法。通过正确地使用信号处理机制,可以提高程序的健壮性和响应能力。希望本文能帮助您更好地理解和应用Linux信号处理,提高开发效率和代码质量。
47 17