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. 使用 JuiceFS 快照功能实现数据库发布与端到端测试

    今天的博客来自 JuiceFS 云服务用户 Jerry,他们通过使用 JuiceFS snapshot 功能,创新性地实现了数据的版本控制.Jerry,是一家位于北美的科技公司,利用人工智能和机器学习 ...

  2. .NET Core 特性(Attribute)底层原理浅谈

    简介 烂大街的资料不再赘述,简单来说就是给代码看的注释 Attribute的使用场景 Attribute不仅仅局限于C#中,在整个.NET框架中都提供了非常大的拓展点,任何地方都有Attribute的 ...

  3. 【一步步开发AI运动小程序】四、小程序如何抽帧

    随着人工智能技术的不断发展,阿里体育等IT大厂,推出的"乐动力"."天天跳绳"AI运动APP,让云上运动会.线上运动会.健身打卡.AI体育指导等概念空前火热.那 ...

  4. MINA 框架简介

    Apache Mina Server 是一个网络通信应用框架,也就是说,它主要是对基于TCP/IP.UDP/IP协议栈的通信框架(然,也可以提供JAVA 对象的序列化服务.虚拟机管道通信服务等),Mi ...

  5. Java实时多任务调度过程中的安全监控设计

    方浩波 (fanghb@eastcom.com)东方通信网络研究所 简介: 在一系列关联的多任务的实时环境中,如果有一个任务发生失败,可能导致所有任务产生连锁反应,从而造成调度失控的局面.特别是对于核 ...

  6. golang定时器之timer+ticker

    转载: https://juejin.cn/post/7327157426298011663 Timer 是一个一次性的定时器,用于在未来的某一时刻执行一次操作. 基本使用 创建 Timer 定时器的 ...

  7. Python 潮流周刊#79:Python 的元数据困境(摘要)

    本周刊由 Python猫 出品,精心筛选国内外的 250+ 信息源,为你挑选最值得分享的文章.教程.开源项目.软件工具.播客和视频.热门话题等内容.愿景:帮助所有读者精进 Python 技术,并增长职 ...

  8. 一文聊清楚Redis主从复制原理

    本地缓存带来的挑战 分布式缓存相比于本地缓存,在实现层面需要关注的点有哪些不同.梳理如下: 维度 本地缓存 集中式缓存 缓存量 受限于单机内存大小,存储数据有限 需要提供给分布式系统里面所有节点共同使 ...

  9. Git 克隆部分文件

    使用 sparse-checkout (推荐) git clone \ -b master \ --depth=1 \ --filter=blob:none \ --sparse \ <repo ...

  10. Winform在主窗体里切换多个窗体

    1.点击解决方案资源管理器的项目名称,右键添加用户控件(Windows窗体). 2.在主窗体代码中实例化添加的用户控件(Windows窗体). 点击查看代码 UserControl1 userCont ...