多线程任务调度系统
ThreadLoom:用 C++17 写一个优先级任务调度器
github地址:https://github.com/johnjiamzhong-project/ThreadLoom
前言
最近用 C++17 从零实现了一个多线程任务调度库——ThreadLoom(线程织机)。名字取"用线程编织任务"之意。这篇文章记录整个设计思路,顺带聊聊实现中几个值得注意的细节。
项目地址:(GitHub 链接可在这里填)
整体架构
submit()
调用方线程 ─────────────► 优先级队列 (pq_)
│
dispatcher_ 线程(唯一消费者)
│
ThreadPool(N 个工作线程)
│
task->execute()
三层结构,职责清晰:
┌────────┬─────────────────────┬──────────────────────────────────────────┐
│ 层 │ 类 │ 职责 │
├────────┼─────────────────────┼──────────────────────────────────────────┤
│ 任务层 | Task / FunctionTask │ 封装可执行逻辑,持有状态、优先级、ID |
├────────┼─────────────────────┼──────────────────────────────────────────┤
│ 调度层 │ Scheduler │ 优先级排序,FIFO 保序,dispatch 线程驱动 │
├────────┼─────────────────────┼──────────────────────────────────────────┤
│ 执行层 │ ThreadPool │ 固定线程数,FIFO 工作队列,空闲检测 │
└────────┴─────────────────────┴──────────────────────────────────────────┘
核心设计决策
- 优先级 + FIFO 双重排序
优先级队列里的元素是 PriorityEntry,除了任务指针还带两个字段:
struct PriorityEntry {
int priority; // Critical=3, High=2, Normal=1, Low=0
uint64_t seqNum; // 全局递增提交序号
std::unique_ptr<Task> task;
bool operator<(const PriorityEntry& o) const {
if (priority != o.priority)
return priority < o.priority; // priority 大的先出
return seqNum > o.seqNum; // seqNum 小的先出(FIFO)
}
};
std::priority_queue 是最大堆,operator< 返回 true 意味着"我比对方优先级低"。这里有一个反直觉的地方:priority 大的应该先出队,所以当 priority < o.priority 时返回 true(我比对方"小",排在后面)。同优先级时
seqNum 小的更早提交,所以同理 seqNum > o.seqNum 时返回 true。
- 调度线程与线程池分离
很多简单实现里,提交任务直接塞进线程池队列,没有优先级可言。ThreadLoom 在线程池前面加了一个专属 dispatcher_
线程,它是优先级队列的唯一消费者,确保"每次只取优先级最高的任务"再交给线程池。线程池本身只是无差别的 FIFO 执行器,不感知优先级。
- 任务状态用原子量
std::atomic
cancel() 通过 CAS 保证只有 Pending 状态能转为 Cancelled,避免已经开始执行的任务被错误取消:
void Task::cancel() {
TaskState expected = TaskState::Pending;
state_.compare_exchange_strong(expected, TaskState::Cancelled);
}
运行中的任务需要在 execute() 内主动调用 isCancelled() 来配合提前退出,协作式取消。
- waitAll() 的实现
等待"队列为空 且 线程池全部空闲",两个条件缺一不可,不然会提前返回:
Scheduler::waitAll()
→ 等待 pq_ 为空
→ ThreadPool::waitIdle()
→ 等待 queue_ 为空 且 activeCount_ == 0
activeCount_ 在工作线程取任务时 +1、执行完后 -1,配合 idleCv_ 条件变量通知 waitIdle()。
使用示例
Scheduler scheduler(4); // 4 个工作线程
// 提交 lambda 任务
scheduler.submit("task-name", [] {
// 你的逻辑
}, TaskPriority::High);
// 提交自定义任务(继承 Task 并实现 execute())
scheduler.submit(std::make_unique<MyTask>("my-task", TaskPriority::Critical));
scheduler.start(); // 开始调度
scheduler.waitAll(); // 等待全部完成
Demo 输出(9 个不同优先级任务,Critical 最先执行):
=== ThreadLoom Scheduler Demo ===
Submitted 9 tasks. Waiting for completion...
[FnTask] Critical priority #1
[FnTask] High priority #1
[Task 7] compute-A => 7^2 = 49
[FnTask] Normal priority #1
[FnTask] Normal priority #2
[Task 8] compute-B => 13^2 = 169
[FnTask] Low priority #1
[FnTask] Low priority #2
[Task 9] compute-C => 42^2 = 1764
=== All tasks completed. Total: 9 ===
当前局限与后续计划
坦白说,这是一个扎实的基础版本,还有几个明显的 gap:
- 任务状态未全程跟踪:Running / Completed / Failed 状态字段存在,但调度器还没在执行前后更新它
- 无异常处理:execute() 抛异常会导致工作线程崩溃
- Scheduler 没有 cancel(id) 接口:Task 层支持取消,但调度器没有暴露按 ID 取消的 API
- 没有 Future/结果回传:目前任务是 fire-and-forget,无法拿到返回值
- 零测试覆盖:后续补 Google Test
后续考虑加延迟任务、动态调整线程数、任务依赖(B 等 A 完成后执行)等特性。
小结
这个项目的价值不在于"又造了个轮子",而在于亲手把调度器里那些教科书级的概念——优先级队列、条件变量、原子操作、协作式取消——串联成一个真实可运行的系统。代码量不大(核心约 400
行)。感兴趣的可以看源码或在评论区交流。
▎ 技术栈:C++17 / CMake / MSVC
▎ 平台:Windows,跨平台理论可行(std::thread 标准库)

浙公网安备 33010602011771号