多线程数据读写顺序处理
一个典型的生产者-消费者模型,在这个模型中,多个工作线程并行处理从共享队列中获取的数据,并将处理结果以保持原始顺序的方式放入另一个队列。
多线程处理模型,具体细节如下:
1.数据:数据里必须有个递增的标识符
和一个结束标识(ending)
2. 读队列(安全队列):用于存放待处理的数据。
-
处理线程:每个线程都是一个死循环
读数据-处理数据-写数据
,它们被编号为1、2、3、4等。这些线程负责从读队列
中取出数据进行处理。线程的结束:判断数据里的ending为true
. -
结果聚合:处理完成后,判断数据的
递增的标识符
,是否为全局的递增的标识符
,如果相等 继续执行。以保持数据的一致性。 -
写队列(安全队列):用于处理好的数据按照读的顺序写入,写入数据到输出队列的顺序是保持一致的。
自定义设计多线程模版:
#include "queuestable.h"
#ifndef QUEUESTABLE_H
#define QUEUESTABLE_H
#include <queue>
#include <mutex>
#include <condition_variable>
#include <functional>
#include <atomic>
#include <type_traits>
#include <memory>
// 基本模板,用于一般类型 必须存在
template <typename T>
struct underlying_type_pointer {
using type = T;
};
// 针对原生指针的特化 默认调用 基本模板
template <typename T>
struct underlying_type_pointer<T*>
{
using type = T;
};
// 针对 std::shared_ptr 的特化 默认调用 基本模板
template <typename T>
struct underlying_type_pointer<std::shared_ptr<T>> {
using type = T;
};
// 针对 std::unique_ptr 的特化 默认调用 基本模板
template <typename T>
struct underlying_type_pointer<std::unique_ptr<T>> {
using type = T;
};
// 提取底层类型的辅助类型别名
template <typename T>
using underlying_type_pointer_t = typename underlying_type_pointer<T>::type;
// 检测是否为 std::shared_ptr
//基本模版用于一般类型 必须存在
template <typename T>
struct is_pointer : std::false_type {
};
// 针对 原生指针* 的特化 默认调用 基本模板
template <typename T>
struct is_pointer<T*> : std::true_type {
};
// 针对 std::shared_ptr 的特化 默认调用 基本模板
template <typename T>
struct is_pointer<std::shared_ptr<T>> : std::true_type {
};
// 针对 std::unique_ptr 的特化 默认调用 基本模板
template <typename T>
struct is_pointer<std::unique_ptr<T>> : std::true_type {
};
// 提取指针类型检查的布尔值别名
template <typename T>
constexpr bool is_pointer_v = is_pointer<T>::value;
//基本数据
struct BaseData
{
/*
* `递增的标识符` sequence_number
* 记录当前位置,保证数据顺序一致
* 这是内部计数,不需要设置,设置也没用,内部会重新排列
*/
int64_t sequence_number = 0;
//true 代表结尾 ,到数据结尾的时候,手动设置为true
bool ending = false;
};
//定义一个线程安全的队列 T必须是指针类型
template <typename T>
class QueueStable
{
// 首先检测 T 是否为原生指针,is_shared_ptr或者is_unique_ptr
static_assert(is_pointer_v<T>, "T must be a pointer!");
// 检测 T 是指向继承自 BaseData 的类的指针
static_assert(std::is_base_of<BaseData, underlying_type_pointer_t<T>>::value, "T must be a pointer to a class derived from BaseData!");
//std::is_pointer 只能判断 * 指针
//static_assert(std::is_pointer<T>::value , "T must be a pointer!");
public:
QueueStable() = default;
QueueStable(const QueueStable<T>&) = delete;
QueueStable& operator=(const QueueStable<T>&) = delete;
QueueStable(unsigned int max_size)
{
m_max_size = max_size;
}
//设置存放数据的最大容量
void set_max_size(unsigned int max_size)
{
m_max_size = max_size;
}
void push(const T& value)
{
std::unique_lock<std::mutex> lock(m_mutex);
//队列大于m_max_size就阻塞
m_cv.wait(lock, [&] {
return m_queue.size() < m_max_size; }); //为 true 继续执行,否则,解锁-》等待。唤醒后,加锁
if(value)
{
//当前序号
value->sequence_number = m_sequence_number;
//下一个数据序号
m_sequence_number += m_step_number;
}
else
{
// 打印异常信息
std::cerr << "QueueStable: value cannot be nullptr." << std::endl;
throw std::runtime_error("value cannot be nullptr.");
}
//入队
m_queue.push(std::move(value));
//m_queue.push(value);
//解锁
lock.unlock();
m_cv.notify_one(); //唤醒另一个
}
T pop()
{
//加锁
std::unique_lock<std::mutex> lock(m_mutex);
m_cv.wait(lock, [&] {
return !m_queue.empty(); }); //为 true 继续执行,否则,解锁-》等待。唤醒后,加锁
//出队
T data = std::move(m_queue.front());
//T data = m_queue.front();
m_queue.pop();
//解锁
lock.unlock();
m_cv.notify_one(); //唤醒另一个
return data;
}
T front() const
{
std::lock_guard<std::mutex> lock(m_mutex);
if(!m_queue.empty())
{
return m_queue.front();
}
return nullptr;
}
//获得第一个数据序号值
int64_t get_start_seq_num()
{
std::lock_guard<std::mutex> lock(m_mutex);
//非空返回第一个数据
if(!m_queue.empty())
{
return m_queue.front()->sequence_number;
}
//返回下一次将要递增的数据
return m_sequence_number;
}
//获得最后一个数据序号值
int64_t get_end_seq_num()
{
std::lock_guard<std::mutex> lock(m_mutex);
return m_sequence_number;
}
//获取增加计数的步长
int32_t get_step()
{
std::lock_guard<std::mutex> lock(m_mutex);
return m_step_number;
}
//设置步长
bool set_step(int32_t step)
{
std::lock_guard<std::mutex> lock(m_mutex);
//数据非空,不能设置,会导致数据序号不是按m_step_number递增
if(!m_queue.empty())
{
return false;
}
m_step_number = step;
return true;
}
bool empty() const
{
std::lock_guard<std::mutex> lock(m_mutex);
return m_queue.empty();
}
int size(