HiJobQueue:一个简单的线程安全任务队列

概述

HiJobQueue 是一个线程安全的任务队列,用于在多线程环境中管理和执行异步任务。它的设计参考了 Cobalt 项目中的 JobQueue,并做了适当的简化。HiJobQueue 提供了任务推送(push)、任务弹出(pop)、队列退出(quit)等功能,适用于需要异步任务调度的场景。


核心功能

  1. 线程安全

    • 使用 std::mutex 和 std::condition_variable 实现线程安全的任务队列。
  2. 任务调度

    • 支持任务的异步推送和弹出。
  3. 退出机制

    • 提供 quit() 方法,用于安全地停止任务队列。
  4. 跨平台

    • 使用 C++ 标准库实现,不依赖平台特定的 API。

实现代码

以下是 HiJobQueue 的实现代码:

#pragma once

#include <mutex>
#include <functional>
#include <queue>
#include <condition_variable> /**
* @brief 线程安全的任务队列,用于管理和执行异步任务。
*/
class HiJobQueue final {
public:
using Job = std::function<void()>; // 任务类型 public:
HiJobQueue() : is_exit_(false) {} /**
* @brief 推送任务到队列。
* @param job 要执行的任务。
* @return 如果队列已退出,返回 false;否则返回 true。
*/
bool push(Job job); /**
* @brief 从队列中弹出任务。
* @param job 用于存储弹出的任务。
* @return 如果队列为空且已退出,返回 false;否则返回 true。
*/
bool pop(Job& job); /**
* @brief 获取队列中的任务数量。
* @return 队列中的任务数量。
*/
size_t size(); /**
* @brief 退出队列,停止任务处理。
*/
void quit(); /**
* @brief 检查队列是否已退出。
* @return 如果队列已退出,返回 true;否则返回 false。
*/
bool is_quited(); // 禁用拷贝构造函数和赋值运算符
HiJobQueue(HiJobQueue&) = delete;
HiJobQueue(const HiJobQueue&) = delete; private:
bool is_exit_; // 队列退出标志
std::mutex mutex_; // 互斥锁,保护队列访问
std::condition_variable cond_; // 条件变量,用于任务通知
std::queue<Job> queue_; // 任务队列
}; // 实现 bool HiJobQueue::push(Job job) {
std::lock_guard<std::mutex> locker(mutex_);
if (is_exit_) {
return false;
}
queue_.push(std::move(job));
cond_.notify_one();
return true;
} bool HiJobQueue::pop(Job& job) {
std::unique_lock<std::mutex> locker(mutex_);
cond_.wait(locker, [this]() { return is_exit_ || !queue_.empty(); });
if (is_exit_ && queue_.empty()) {
return false;
}
job = std::move(queue_.front());
queue_.pop();
return true;
} size_t HiJobQueue::size() {
std::lock_guard<std::mutex> locker(mutex_);
return queue_.size();
} void HiJobQueue::quit() {
std::lock_guard<std::mutex> locker(mutex_);
is_exit_ = true;
cond_.notify_all();
} bool HiJobQueue::is_quited() {
std::lock_guard<std::mutex> locker(mutex_);
return is_exit_;
}

测试用例

为了验证 HiJobQueue 的正确性和线程安全性,我们设计了以下测试用例:

测试代码

#include <gtest/gtest.h>
#include <future>
#include <atomic>
#include <thread>
#include <chrono>
#include "hi_job_queue.h" class TestCls {
public:
void test(const char* text, int i) {
printf("%s-%d\n", text, i);
}
}; TEST(HiJobQueueTest, ConcurrentPushPop) {
HiJobQueue queue;
TestCls cls; std::atomic<int> job_count{0}; // 用于统计执行的任务数量 // 启动两个线程消费任务
auto f1 = std::async(std::launch::async, [&] {
HiJobQueue::Job job;
while (queue.pop(job)) {
job();
job_count++;
}
}); auto f2 = std::async(std::launch::async, [&] {
HiJobQueue::Job job;
while (queue.pop(job)) {
job();
job_count++;
}
}); // 启动两个线程生产任务
auto f3 = std::async(std::launch::async, [&] {
for (int i = 0; i < 200; i++) {
queue.push(std::bind(&TestCls::test, &cls, "test1", i));
std::this_thread::sleep_for(std::chrono::milliseconds(5)); // 跨平台休眠
}
}); auto f4 = std::async(std::launch::async, [&] {
for (int i = 0; i < 200; i++) {
queue.push(std::bind(&TestCls::test, &cls, "test2", i));
std::this_thread::sleep_for(std::chrono::milliseconds(5)); // 跨平台休眠
}
}); // 等待生产任务完成
f3.wait();
f4.wait(); // 退出队列
queue.quit(); // 等待消费任务完成
f1.wait();
f2.wait(); // 验证所有任务被执行
EXPECT_EQ(job_count.load(), 400); // 200 (test1) + 200 (test2)
} TEST(HiJobQueueTest, QuitBehavior) {
HiJobQueue queue; // 启动一个线程消费任务
auto consumer = std::async(std::launch::async, [&] {
HiJobQueue::Job job;
while (queue.pop(job)) {
job();
}
}); // 推送一些任务
for (int i = 0; i < 10; i++) {
queue.push([]() {});
} // 退出队列
queue.quit(); // 等待消费线程结束
consumer.wait(); // 验证队列已退出
EXPECT_TRUE(queue.is_quited()); // 验证退出后不能再 push 任务
EXPECT_FALSE(queue.push([]() {}));
} TEST(HiJobQueueTest, EmptyQueueBehavior) {
HiJobQueue queue; // 验证队列为空时的 pop 行为
HiJobQueue::Job job;
EXPECT_FALSE(queue.pop(job)); // 退出队列
queue.quit(); // 验证退出后 pop 行为
EXPECT_FALSE(queue.pop(job));
}

测试用例说明

  1. ConcurrentPushPop

    • 测试多线程环境下 push 和 pop 的并发行为。
    • 验证所有任务是否被正确执行。
  2. QuitBehavior

    • 测试队列退出时的行为。
    • 验证退出后是否不再接受新任务。
  3. EmptyQueueBehavior

    • 测试队列为空时的行为。
    • 验证退出后 pop 的行为。

适用场景

HiJobQueue 适用于以下场景:

  1. 多线程任务调度

    • 在需要将任务分发到多个工作线程执行的场景中,HiJobQueue 可以作为任务调度器使用。
    • 例如:线程池中的任务队列。
  2. 事件驱动架构

    • 在事件驱动的系统中,HiJobQueue 可以用于存储和处理事件。
    • 例如:GUI 应用中的事件队列。
  3. 异步任务处理

    • 在需要异步执行任务的场景中,HiJobQueue 可以用于存储任务并由后台线程处理。
    • 例如:日志系统的异步写入。
  4. 生产者-消费者模型

    • 在生产者-消费者模型中,HiJobQueue 可以作为共享的任务缓冲区。
    • 例如:多线程下载任务的分发。

优缺点分析

优点

  1. 线程安全

    • 使用 std::mutex 和 std::condition_variable 确保多线程环境下的安全性。
  2. 简单易用

    • 提供了简洁的接口(pushpopquit),易于集成到现有项目中。
  3. 跨平台

    • 基于 C++ 标准库实现,不依赖平台特定的 API,具有良好的可移植性。
  4. 退出机制

    • 提供 quit() 方法,可以安全地停止任务队列,避免资源泄漏。
  5. 轻量级

    • 代码简洁,性能开销小,适合对性能要求较高的场景。

缺点

  1. 功能单一

    • 仅支持基本的任务队列功能,不支持优先级调度或任务取消。
  2. 性能瓶颈

    • 在高并发场景下,std::mutex 可能成为性能瓶颈。
    • 如果需要更高的性能,可以考虑无锁队列(如 boost::lockfree::queue)。
  3. 任务类型限制

    • 任务类型为 std::function<void()>,不支持返回值或参数传递。
    • 如果需要更复杂的任务类型,需要自行扩展。
  4. 缺乏任务状态管理

    • 不支持任务的状态管理(如任务完成通知或错误处理)。

总结

HiJobQueue 是一个简单但功能强大的线程安全任务队列,适用于多线程环境中的异步任务调度。通过参考 Cobalt 项目中的 JobQueue,我们实现了一个更轻量级的版本,并通过单元测试验证了其正确性和线程安全性。希望这篇文章能帮助你理解和使用 HiJobQueue

HiJobQueue:一个简单的线程安全任务队列的更多相关文章

  1. Linux C 一个简单的线程池程序设计

    最近在学习linux下的编程,刚开始接触感觉有点复杂,今天把线程里比较重要的线程池程序重新理解梳理一下. 实现功能:创建一个线程池,该线程池包含若干个线程,以及一个任务队列,当有新的任务出现时,如果任 ...

  2. linux网络编程-一个简单的线程池(41)

    有时我们会需要大量线程来处理一些相互独立的任务,为了避免频繁的申请释放线程所带来的开销,我们可以使用线程池 1.线程池拥有若干个线程,是线程的集合,线程池中的线程数目有严格的要求,用于执行大量的相对短 ...

  3. 【Linux】一个简单的线程创建和同步的例子

    最近很多精力在Linux上,今天简单看了一下Linux上的线程和同步,其实不管windows还是Linux,OS层面的很多原理和概念都是相同的,很多windows之上的经验和概念完全可以移植到Linu ...

  4. Linux C 实现一个简单的线程池

    线程池的定义 线程池是一种多线程处理形式,处理过程中将任务添加到队列,然后在创建线程后自动启动这些任务.线程池线程都是后台线程.每个线程都使用默认的堆栈大小,以默认的优先级运行,并处于多线程单元中.如 ...

  5. Java一个简单的线程池实现

    线程池代码 import java.util.List; import java.util.Vector; public class ThreadPool  {     private static  ...

  6. Java多线程-一个简单的线程,实现挂起和恢复的功能

    public class MySprite implements Runnable { /* * 线程用变量 */ private boolean running = false; private b ...

  7. 一个简单的python线程池框架

    初学python,实现了一个简单的线程池框架,线程池中除Wokers(工作线程)外,还单独创建了一个日志线程,用于日志的输出.线程间采用Queue方式进行通信. 代码如下:(不足之处,还请高手指正) ...

  8. 【C/C++开发】C++实现简单的线程池

    C++实现简单的线程池 线程池编程简介: 在我们的服务端的程序中运用了大量关于池的概念,线程池.连接池.内存池.对象池等等.使用池的概念后可以高效利用服务器端的资源,比如没有大量的线程在系统中进行上下 ...

  9. 【C/C++开发】C++实现简单的线程类

    C++封装一个简单的线程类 多线程编程简介: 大家在编程时,经常需要在程序中启动一个或多个线程来处理任务,而如果每次都是去调用系统创建线程的API函数来创建,代码量虽不多,但线程的创建和业务逻辑代码就 ...

  10. 一个简单的linux线程池(转-wangchenxicool)

    线程池:简单地说,线程池 就是预先创建好一批线程,方便.快速地处理收到的业务.比起传统的到来一个任务,即时创建一个线程来处理,节省了线程的创建和回收的开销,响应更快,效率更高. 在linux中,使用的 ...

随机推荐

  1. OpenGL编程指南(原书第9版)

    这本书是<OpenGL编程指南(原书第9版)>,也称为<OpenGL Programming Guide: The Official Guide to Learning OpenGL ...

  2. 一个基于 .NET 8.0 构建的简单、跨平台、模块化商城系统

    前言 今天给大家分享一个基于 .NET 8.0 构建的开源免费(MIT License).简单.跨平台.模块化的商城系统:Module Shop. 主要功能 销售:订单.物流. 内容:首页配置.评论. ...

  3. 使用缓存构建更快的 Web 应用程序

    使用 Java 缓存系统缓存频繁查看的数据 使用 Java 技术的 Web 开发人员可以使用缓存实用程序快速提升他们的应用程序的性能.Java 缓存系统(Java Caching System,JCS ...

  4. ECharts 标题组件

    1.标题组件的基本使用 图标组件使用title节点进行配置. 标题分为主标题和副标题, 主标题的文本内容使用 'text' 属性进行设置 副标题使用 'subtext' 属性进行设置 var opti ...

  5. Skyvern – AI浏览器自动化测试工具

    Skyvern – AI浏览器自动化测试工具 ​​ ‍ Skyvern是什么 Skyvern是开源的浏览器自动化工具,结合大型语言模型(LLMs)和计算机视觉技术实现复杂的网页交互和数据提取.与传统的 ...

  6. HarmonyOS Next 入门实战 - 基础组件、页面实现

    基础组件 常用组件 Text:显示文本内容 Image:显示图片 Button:显示一个按钮 Column: 纵向布局 Row:横向布局 List:列表 各组件的用法 Text("文本组件& ...

  7. SQL SERVER日常运维巡检系列——数据库备份

    前言 做好日常巡检是数据库管理和维护的重要步骤,而且需要对每次巡检日期.结果进行登记,同时可能需要出一份巡检报告. 本系列旨在解决一些常见的困扰: 不知道巡检哪些东西 不知道怎么样便捷体检 机器太多体 ...

  8. 微信开发者工具请求接口 Provisional headers are shown

    前情 最近全权负责公司小程序项目的开发,使用的uniapp技术栈. 坑 在和服务端联调的时候发现,接口pending很久,而且时不时的报Provisional headers are shown,而且 ...

  9. vue3笔记

    如何创建vue3项目 基于 vue 脚手架 npm i @vue/cli -g vue create <project-name> cd <project-name> npm ...

  10. NATS: 对依赖注入支持

    NuGet: NATS.Net 使用方法: serviceCollection.AddNats(); 在容器中添加了 2 个单例服务: NATS.Client.Core.NatsConnection ...