文章目录
0. ConsumerProducer库概述
ConsumerProducer库是一个用于多线程任务处理的C++库。它提供了一种机制,允许用户定义任务的优先级和处理方式,并通过多线程方式高效地处理任务队列中的任务。
- 代码符合Misra C++标准;
- 模块提供设置线程优先级、处理线程个数及任务队列个数的功能;
- 模块提供低优先级队列和高优先级队列管理功能。
- 生产者添加任务的时候会根据优先级添加到低优先级队列还是高优先级队列,消费者获取任务的时候优先获取高优先级队列中的任务进行处理。
- 模块具有统计任务总的等待时间消耗,处理时间消耗、丢弃时间消耗信息的功能。
完整代码路径:https://gitee.com/liudegui/consumer-producer
1. 使用示例代码如下
#include "consumer_producer.hpp"
#include <chrono>
#include <iostream>
#include <string>
#include <utility>
// Define your worker class if it's not provided
struct YourWorker {
int id;
std::string data;
// Add necessary members and methods
void process() {
// Implement processing logic
std::cout << "Processing data: " << data << std::endl;
}
};
int32_t consume_func(std::shared_ptr<YourWorker> in_job, bool prefer) {
// Implement your consume logic here
// This function will be called by the ConsumerProducer
if (in_job != nullptr) {
in_job->process(); // Process the job
return 0; // Return appropriate value based on your logic
} else {
return -1; // Indicate failure if job is nullptr
}
}
int main() {
// Define your configuration
MyTest::ConsumerProducerConfig config;
config.in_name = "YourConsumerProducer"; // Name of the ConsumerProducer
config.in_priority = 0; // Priority of the threads
config.in_worker_num = 4; // Number of threads to create
config.in_queue_size = 100; // Size of the queue for jobs
config.in_prefer_queue_size = 10; // Size of the preferred jobs in the low priority queue
config.in_hi_cp_queue_size = 20; // Size of the queue for high priority jobs
config.in_cp_cpu_set_size = 0; // Size of the CPU set (0 for default)
config.in_cp_cpuset = nullptr; // CPU set to bind the threads (nullptr for default)
config.in_allow_log = true; // Allow logging
// Create an instance of ConsumerProducer
MyTest::ConsumerProducer<YourWorker> consumerProducer(config, consume_func);
// Start processing jobs
consumerProducer.start_process();
// Add some jobs to the queue
for (int i = 0; i < 50; ++i) {
std::shared_ptr<YourWorker> job_ptr = std::make_shared<YourWorker>();
job_ptr->id = i;
job_ptr->data = "Data " + std::to_string(i);
consumerProducer.add_job(std::move(job_ptr)); // Transfer ownership
}
// Wait for some time or do other operations
// Shutdown the threads
consumerProducer.shutdown_threads();
return 0;
}
2. ConsumerProducerConfig结构体
首先,让我们来看一下ConsumerProducerConfig结构体。这个结构体定义了任务处理的配置参数,如输入队列的名称、优先级、工作线程数量等。通过配置这些参数,用户可以根据实际需求灵活地调整任务处理的行为。
3. ConsumerProducer类
ConsumerProducer类是ConsumerProducer库的核心组件,它提供了任务的添加、处理和管理功能。该类包含了任务队列、线程管理等重要功能,下面我们将详细介绍其主要成员和功能。
4. MyJob结构体
MyJob结构体表示一个任务,包含了任务的相关信息,如任务指针、时间戳、任务ID等。通过MyJob结构体,用户可以轻松地管理和操作任务。
5. MyCpQueue类
MyCpQueue类实现了一个任务队列,用于存储任务并支持任务的添加和弹出操作。该类采用循环队列的方式实现,保证了任务的高效处理。
6. ConsumerProducer类成员函数
ConsumerProducer类提供了一系列成员函数,用于任务的添加、处理和管理。这些函数包括添加任务、启动处理线程、暂停任务处理等,为用户提供了丰富的操作接口。
函数名称 | 功能 |
---|---|
ConsumerProducer:: ConsumerProducer | 构造函数 |
ConsumerProducer::add_job | 添加job到队列 |
ConsumerProducer:: add_job_wait_done | 添加job并等待任务完成 |
ConsumerProducer:: shutdown | 关闭线程 |
ConsumerProducer:: queue_length | 获取队列中存在的job数量 |
ConsumerProducer:: max_queue_length | 获取队列可以存放的最大job数量 |
ConsumerProducer:: get_threads | 获取线程池中存放线程容器的首地址 |
ConsumerProducer:: dropped_job_count | 获取丢弃的job数量 |
ConsumerProducer:: blocked_job_count | 获取被阻塞的job数量 |
ConsumerProducer:: print_stats | 打印线程池状态信息 |
7. 核心代码
struct ConsumerProducerConfig {
std::string in_name;
int32_t in_priority;
uint32_t in_worker_num;
uint32_t in_queue_size;
uint32_t in_prefer_queue_size;
uint32_t in_hi_cp_queue_size;
uint32_t in_cp_cpu_set_size;
const cpu_set_t *in_cp_cpuset;
bool in_allow_log;
};
template <typename Worker>
class ConsumerProducer {
public:
/**
* @brief ConsumerProducer process callback function
* @param job MyJob to process
* @param prefer True if the job is preferred, otherwise false
*/
using ConsumeFunc = std::function<int32_t(std::shared_ptr<Worker> job, bool prefer)>;
private:
enum MyJobPriority : int32_t {
PRIORITY_LOW = 0,
PRIORITY_HIGH = 1,
PRIORITY_MAX = 2,
};
class MyJob {
private:
std::shared_ptr<Worker> my_job_ptr_;
int64_t job_timestamp_;
uint64_t job_job_id_;
bool job_not_discardable_;
public:
MyJob() : my_job_ptr_(nullptr), job_timestamp_(0), job_job_id_(0), job_not_discardable_(false) {
}
MyJob(std::shared_ptr<Worker> job_task, int64_t in_timestamp, uint64_t job_id, bool not_discardable)
: my_job_ptr_(job_task),
job_timestamp_(in_timestamp),
job_job_id_(job_id),
job_not_discardable_(not_discardable) {
}
public:
inline bool is_not_discardable() const {
return job_not_discardable_;
}
inline uint64_t get_job_id() const {
return job_job_id_;
}
inline int64_t get_timestamp() const {
return job_timestamp_;
}
std::shared_ptr<Worker> get_my_job() const {
return my_job_ptr_;
}
};
class MyCpQueue {
private:
std::string cpqueue_name_;
uint32_t cpqueue_head_{0};
uint32_t cpqueue_tail_{0};
bool cpqueue_full_{false};
bool cpqueue_empty_{true};
uint32_t cpqueue_queue_size_{0};
uint32_t cpqueue_count_{0};
std::vector<MyJob> jobs_;
bool cp_queue_allow_log_{false};
public:
MyCpQueue(const std::string &in_name, uint32_t in_queue_size, bool in_allow_log) {
cpqueue_name_ = in_name;
cpqueue_queue_size_ = in_queue_size;
jobs_.resize(in_queue_size);
cp_queue_allow_log_ = in_allow_log;
}
~MyCpQueue() {
if (cp_queue_allow_log_) {
if (cpqueue_count_ > 0) {
MY_LOG_ERROR("%s cpqueue_count_ = %d", cpqueue_name_.c_str(), cpqueue_count_);
}
}
}
bool is_full() const {
return cpqueue_full_;
}
bool cpq_is_empty() const {
return cpqueue_empty_;
}
uint32_t cp_queue_queue_length() const {
return cpqueue_count_;
}
void cpq_add_job(const MyJob &in_job) {
jobs_[cpqueue_tail_] = in_job;
cpqueue_tail_++;
if (cpqueue_tail_ == cpqueue_queue_size_) {
cpqueue_tail_ = 0;
}
if (cpqueue_tail_ == cpqueue_head_) {
cpqueue_full_ = true;
}
cpqueue_empty_ = false;
cpqueue_count_++;
if (cp_queue_allow_log_) {
if (cpqueue_count_ > cpqueue_queue_size_) {
MY_LOG_PANIC("%s cpqueue_count_ = %u cpqueue_queue_size_ = %u", cpqueue_name_.c_str(), cpqueue_count_,
cpqueue_queue_size_);
}
}
return;
}
uint32_t pop(MyJob &out_job, bool peek_only = false) {
uint32_t ret_value = cpqueue_count_;
if (cpqueue_empty_) {
if (cp_queue_allow_log_) {
if (cpqueue_count_ > 0) {
MY_LOG_PANIC("%s cpqueue_count_ = %u", cpqueue_name_.c_str(), cpqueue_count_);
}
}
ret_value = 0;
} else {
if (cp_queue_allow_log_) {
if (cpqueue_count_ == 0) {
MY_LOG_PANIC("%s cpqueue_count_ = %u", cpqueue_name_.c_str(), cpqueue_count_);
}
} else {
}
out_job = jobs_[cpqueue_head_];
if (peek_only == false) {
cpqueue_head_++;
if (cpqueue_head_ == cpqueue_queue_size_) {
cpqueue_head_ = 0;
}
cpqueue_count_--;
if (cpqueue_head_ == cpqueue_tail_) {
cpqueue_empty_ = true;
if (cp_queue_allow_log_) {
if (cpqueue_count_ != 0) {
MY_LOG_PANIC("%s cpqueue_count_ = %u", cpqueue_name_.c_str(), cpqueue_count_);
}
}
}
}
}
cpqueue_full_ = false;
return ret_value;
}
inline uint32_t peek(MyJob &out_job) {
return pop(out_job, true);
}
};
private:
std::string cp_name_;
int32_t cp_priority_;
MyCpQueue cp_queue_;
std::atomic_bool blocking_;
std::atomic_bool paused_;
uint32_t cp_prefer_queue_size_;
MyCpQueue cp_hi_queue_;
uint32_t cp_worker_num_;
std::vector<std::thread> threads_;
ConsumeFunc cp_consume_func_;
std::mutex cp_mutex_;
std::condition_variable cp_not_full_;
std::condition_variable cp_not_empty_;
std::condition_variable cp_job_done_cond_;
std::atomic_int cp_started_;
std::atomic_bool cp_shutdown_;
uint32_t cp_cpusetsize_;
const cpu_set_t *cp_cpuset_ = nullptr;
bool cp_allow_log_;
int64_t start_time_;
int64_t total_wait_time_[PRIORITY_MAX];
int64_t total_process_time_[PRIORITY_MAX];
int64_t total_drop_time_[PRIORITY_MAX];
uint64_t added_job_[PRIORITY_MAX];
uint64_t finished_job_[PRIORITY_MAX];
uint64_t blocked_job_[PRIORITY_MAX];
uint64_t dropped_job_[PRIORITY_MAX];
uint32_t cp_job_id_[PRIORITY_MAX];
uint64_t finished_job_id_[PRIORITY_MAX];
uint32_t max_queue_length_;
int64_t last_active_time_;
int64_t last_elapse_time_;
int32_t cp_pid_{0};
private:
/**
* @brief Get the job id of corresponding priority
* @param arry_idx Index of the priority
* @return Return the job id of corresponding priority
*/
uint32_t assign_job_id_(uint64_t arry_idx) {
return ++cp_job_id_[arry_idx];
}
/**
* @brief Mark the last finished job id of corresponding priority
* @param arry_idx Index of the priority
* @param id MyJob id to mark
*/
void update_done_job_id_(uint64_t arry_idx, uint64_t id_in) {
finished_job_id_[arry_idx] = id_in;
}
/**
* @brief Get a job from high priority queue or low priority queue
* @param job Buffer to store the job
* @param priorty Buffer to store the priority of the job
* @return Return the queue size where the job is from
*/
uint32_t get_job_(MyJob &out_job, MyJobPriority &priority_out) {
bool exit_flag = false;
uint32_t ret_value = 0;
while (exit_flag == false) {
std::unique_lock<std::mutex> lck(cp_mutex_);
if (cp_queue_.cpq_is_empty() && cp_hi_queue_.cpq_is_empty() && (cp_shutdown_.load() == false)) {
cp_not_empty_.wait(lck);
}
ret_value = cp_hi_queue_.pop(out_job);
if (ret_value == 0) {
priority_out = PRIORITY_LOW;
ret_value = cp_queue_.pop(out_job);
} else {
priority_out = PRIORITY_HIGH;
}
lck.unlock();
if (ret_value != 0) {
cp_not_full_.notify_all();
exit_flag = true;
} else {
if (cp_shutdown_) {
cp_not_empty_.notify_all();
exit_flag = true;
ret_value = 0;
} else {
// do nothing
}
}
}
return ret_value;
}
/**
* @brief Get the total number of added jobs
* @return Return the total number of added jobs
*/
uint64_t added_job_total_() const {
uint64_t ret_value = 0;
for (int32_t arry_index = 0; arry_index < PRIORITY_MAX; arry_index++) {
ret_value += added_job_[arry_index];
}
return ret_value;
}
/**
* @brief Get the total number of finished jobs
* @return Return the total number of finished jobs
*/
uint64_t finished_job_total_() const {
uint64_t ret_value = 0;
for (int32_t arry_index = 0; arry_index < PRIORITY_MAX; arry_index++) {
ret_value += finished_job_[arry_index];
}
return ret_value;
}
/**
* @brief Get the total number of dropped jobs
* @return Return the total number of dropped jobs
*/
uint64_t dropped_job_total_() const {
uint64_t ret_value = 0;
for (uint32_t arry_index = 0; arry_index < PRIORITY_MAX; arry_index++) {
ret_value += dropped_job_[arry_index];
}
return ret_value;
}
public:
static void *consumer_thread_func_(ConsumerProducer *in_context);
public:
/**
* @brief ConsumerProducer ructor.
* MyJob in high priority queue will be processed first, and will be marked as preferred.
* MyJob in low priority queue will be marked as preferred
* if the size of low priority queue is less or equal than prefer_queue_size,
* others will be marked as not preferred.
* @param name Name of the ConsumerProducer
* @param priority MyJobPriority of the threads
* @param worker_num Number of threads to create
* @param consume_func Process function of the threads
* @param consume_context Context of the process function
* @param queue_size Size of the queue for jobs
* @param prefer_queue_size Size of the prefered jobs in the low priority queue
* @param hi_cp_queue_size Size of the queue for high priority jobs
* @param cpusetsize Size of the CPU set
* @param cpuset CPU set to bind the threads
* @param cp_allow_log_ Allow logging
*/
explicit ConsumerProducer(const ConsumerProducerConfig &in_config, const ConsumeFunc &in_consume_func);
~ConsumerProducer() {
if (cp_allow_log_) {
if ((!cp_shutdown_.load()) || paused_.load()) {
MY_LOG_ERROR("%s shutdown=%d paused=%d started=%d", cp_name_.c_str(), cp_shutdown_.load(), paused_.load(),
cp_started_.load());
}
}
}
/**
* @brief Add job to the queue
* @param in MyJob to add_job
* @param high_priority True if the job is high priority, otherwise false.
* @param job_id_out Buffer to store job id of the added job
* @param not_discardable True if the job is not discardable, otherwise false.
* @return Return 0: normal enqueue
* 1: block enqueue
* 2: give up enqueue
* 3: discard head and enqueue
*/
int32_t add_job_do_(std::shared_ptr<Worker> job, bool high_priority, uint64_t *const job_id_out,
bool not_discardable);
/**
* @brief Create threads and start processing jobs
*/
void start_process() {
cp_started_++;
cp_shutdown_ = false;
// creates threads
threads_.reserve(cp_worker_num_);
for (uint64_t arry_index = 0; arry_index < cp_worker_num_; arry_index++) {
threads_.emplace_back(consumer_thread_func_, this);
}
std::this_thread::sleep_for(std::chrono::milliseconds(10));
}
/**
* @brief Add job to the queue
* @param in_job MyJob to add_job
* @param high_priority True if the job is high priority, otherwise false.
* High priority job will be added to high priority queue,
* otherwise low priority queue.
* If the high priority queue is full, the high priority job will wait until
* the queue is not full.
* @param not_discardable True if the job is not discardable, otherwise false.
* If the low priority queue is full, and not_discardable is true,
* the low priority job will wait until the queue is not full.
* If the low priority queue is full, and not_discardable is false,
* then will comsume the oldest job if possible, otherwise will cosume the
* current job immediately.
* @return Return 0: normal enqueue
* 1: block enqueue
* 2: give up enqueue
* 3: discard head and enqueue
*/
inline void add_job(std::shared_ptr<Worker> in_job, bool high_priority = false, bool not_discardable = false);
/**
* @brief Add job to the quque and wait until the job is processed
* @param in_job MyJob to add_job
* @param high_priority True if the job is high priority, otherwise false.
* @return Return 0: normal enqueue
* 1: block enqueue
* 2: give up enqueue
* 3: discard head and enqueue
*/
int32_t add_job_wait_done(std::shared_ptr<Worker> in_job, bool high_priority = false);
/**
* @brief Shutdown the threads and wait until all jobs are stopped
*/
void shutdown_threads();
/**
* @brief Pause adding jobs to the queue, and wait until all jobs are processed or dropped
*/
void flush_and_pause();
/**
* @brief Resume adding jobs to the queue
*/
void resume();
/**
* @brief Get the normal queue length
* @return Return the normal queue length
*/
inline uint64_t queue_length() {
return cp_queue_.cp_queue_queue_length();
}
/**
* @brief Get the normal queue max length
* @return Return the normal queue max length
*/
inline uint64_t max_queue_length() {
return max_queue_length_;
}
/**
* @brief Get the total number of dropped jobs
* @return Return the total number of dropped jobs
*/
inline uint64_t dropped_job_count();
/**
* @brief Get the total number of blocked jobs
* @return Return the total number of blocked jobs
*/
inline uint64_t blocked_job_count();
/**
* @brief Print current status
*/
void print_stats(void);
/**
* @brief Get current status string
* @param str_buf Buffer to store the status string
*/
void get_stats_string(std::string &output_buffer);
};