一、背景
考虑有多个任务,比如有5个数据采集设备,需要对每个设备采集的数据进行处理,单个设备的数据需要按照数据产生的顺序进行处理,每个设备产生的频率不一致。应该如何处理上述需求?
- 方案一:
多线程处理:对每个设备采用一个线程进行处理,确保了设备的数据顺序。
弊端:当设备增加时,浪费了大量线程;对于频率低的设备线程利用率低,对于频率高的设备处理能力可能不足。 - 方案二:
线程池处理:虽然能很好的利用资源,但无法保证单个设备的数据有序性。 - 方案三:
协程:协程能较好的处理此问题,但需要C++20支持,且无栈协程具有传染性。
基于上述情况,提出了一种按照任务组进行顺序执行的线程池。
二、简介
基于C++11实现的一个head-only的线程池。其区别于普通线程池在于:可以对任务进行分组,同一个组内的任务按照入队顺序进行执行,不同组之间的任务抢占式执行。线程资源平衡利用,避免出现某个线程利用率高,某个线程利用率低的问题。
三、实现原理
将任务分组放入对应任务组队列,每次从其中一个任务队列中取走一个任务,此时任务当前任务组处于锁定,当此任务完成后此任务组解锁,可再次取走任务进行执行。详细步骤图解如下:
- 添加任务时,将任务按照指定的组放入相应队列。如下图所示,将任务放入ABC三个任务组,同时将ABC三个组加入可运行任务组队列。(可运行任务组队列表示当前处于可运行状态的任务分组)。
- 从可运行任务组队列中取出可执行的任务组标识,并从对应任务组取走任务进行执行。下图中取走A1和B1任务进行执行,此时任务组A和任务组B处于不可继续取任务状态。(两个线程同时运行任务,故取走了两个任务)。
- 任务B1完成执行后,此时任务组B又处于可运行状态,将任务组B加入可运行任务组队列。
- 此时只有一个任务处于运行状态,故根据可运行任务组中的指示取走任务组C中的一个任务C1进行执行。
- 循环执行以上步骤,即可实现任务分组有序执行。
四、源码
核心源码
- 添加任务
void enqueue(int group, std::function<void()> task) {
bool waiting{};
{
std::lock_guard<std::mutex> lock(mutex_);
waiting = group_tasks_[group].empty(); // 任务组为空,说明此任务组没有任务正在执行
group_tasks_[group].emplace(std::move(task)); // 将任务放入相应组
if (waiting)
runnable_group_.push(group); // 将任务组ID加入可运行任务组队列
}
if (waiting)
condition_.notify_one();
}
- 执行任务
void run() {
while (stop_token_) {
uint32_t group{};
std::function<void()> task{};
// 根据可运行任务组队列取走任务
{
std::unique_lock<std::mutex> lock(mutex_);
if (!condition_.wait_for(lock, std::chrono::seconds(1), [this]() { return !runnable_group_.empty(); })) {
continue;
}
group = runnable_group_.front(); // 当前可运行的任务组ID
runnable_group_.pop();
task = std::move(group_tasks_[group].front()); // 从对应任务组中取走任务
}
// 执行任务
task();
bool notify{};
{
std::lock_guard<std::mutex> lock(mutex_);
group_tasks_[group].pop();
// 将当前任务组ID加入可运行任务组队列
if (!group_tasks_[group].empty()) {
runnable_group_.push(group);
notify = true;
}
}
if (notify)
condition_.notify_one();
}
}
最新源码见Github: SequentialThreadPool
五、使用
5.1 示例代码
#include "SequentialThreadPool.hpp"
#include <iostream>
#include <sstream>
int main() {
const auto start = std::chrono::high_resolution_clock::now();
SequentialThreadPool pool(4);
for (int task = 0; task < 30; ++task) {
const int group = task % 5; // Task Grouping
pool.enqueue(group, [=]() {
const auto end = std::chrono::high_resolution_clock::now();
const auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(end - start).count();
const int sleep = task == 0 ? 200 : rand() % 100 + 50;
std::stringstream ss;
ss << "Time:" << duration << ", group:" << group << ", task:" << task
<< ", tid:" << std::this_thread::get_id() << ", sleep:" << sleep << "ms\n";
std::cout << ss.str();
std::this_thread::sleep_for(std::chrono::milliseconds(sleep));
});
}
return 0;
}
5.2 示例输出
Time:5, group:0, task:0, tid:23788, sleep:200ms
Time:5, group:1, task:1, tid:26944, sleep:91ms
Time:5, group:2, task:2, tid:27324, sleep:91ms
Time:5, group:3, task:3, tid:21836, sleep:91ms
Time:105, group:4, task:4, tid:27324, sleep:117ms
Time:105, group:3, task:8, tid:26944, sleep:117ms
Time:105, group:2, task:7, tid:21836, sleep:117ms
Time:216, group:1, task:6, tid:23788, sleep:91ms
Time:231, group:0, task:5, tid:27324, sleep:84ms
Time:231, group:3, task:13, tid:26944, sleep:84ms
Time:231, group:2, task:12, tid:21836, sleep:84ms
Time:324, group:4, task:9, tid:21836, sleep:50ms
Time:324, group:2, task:17, tid:23788, sleep:117ms
Time:324, group:1, task:11, tid:27324, sleep:50ms
Time:324, group:0, task:10, tid:26944, sleep:50ms
Time:386, group:0, task:15, tid:27324, sleep:119ms
Time:386, group:4, task:14, tid:21836, sleep:119ms
Time:386, group:3, task:18, tid:26944, sleep:119ms
Time:449, group:1, task:16, tid:23788, sleep:84ms
Time:513, group:0, task:20, tid:26944, sleep:74ms
Time:513, group:3, task:23, tid:21836, sleep:74ms
Time:513, group:2, task:22, tid:27324, sleep:74ms
Time:543, group:4, task:19, tid:23788, sleep:50ms
Time:589, group:1, task:21, tid:21836, sleep:128ms
Time:589, group:3, task:28, tid:27324, sleep:128ms
Time:589, group:2, task:27, tid:26944, sleep:128ms
Time:605, group:0, task:25, tid:23788, sleep:119ms
Time:728, group:1, task:26, tid:23788, sleep:74ms
Time:728, group:4, task:24, tid:21836, sleep:108ms
Time:838, group:4, task:29, tid:21836, sleep:112ms
5.3 示例任务执行顺序图
基于上述执行进行进行图形化后如下,组内任务顺序执行,组间任务并行执行。