docs: rebuild Threading API content
This commit is contained in:
@@ -0,0 +1,162 @@
|
||||
# Synchronization And Task System Limits
|
||||
|
||||
## 先建立正确的心智模型
|
||||
|
||||
`XCEngine::Threading` 当前不是一套已经完整收口的商业级并发框架,而是三层能力并存:
|
||||
|
||||
- 同步原语层: [Mutex](../../XCEngine/Threading/Mutex/Mutex.md)、[SpinLock](../../XCEngine/Threading/SpinLock/SpinLock.md)、[ReadWriteLock](../../XCEngine/Threading/ReadWriteLock/ReadWriteLock.md)
|
||||
- 线程包装层: [Thread](../../XCEngine/Threading/Thread/Thread.md)
|
||||
- 任务抽象层: [ITask](../../XCEngine/Threading/Task/Task.md)、[TaskGroup](../../XCEngine/Threading/TaskGroup/TaskGroup.md)、[TaskSystem](../../XCEngine/Threading/TaskSystem/TaskSystem.md)
|
||||
|
||||
这三层同时存在是合理的。商业引擎里也几乎从来不是“有了 job system 就不要锁了”,原因很简单:
|
||||
|
||||
- 平台窗口消息、日志、资源表、对象注册表,仍然需要细粒度同步。
|
||||
- 有些后台工作需要明确线程生命周期,而不是匿名 job。
|
||||
- 任务系统更多解决吞吐量和调度问题,不会替代所有底层同步。
|
||||
|
||||
## 为什么要同时保留锁、线程和任务系统
|
||||
|
||||
可以把它类比成很多商业引擎的常见分层:
|
||||
|
||||
- 最底层保留 `mutex`、`rw lock`、原子变量等基础设施。
|
||||
- 中间层保留明确的 worker thread 或 service thread,例如文件监控线程、日志线程、调试采样线程。
|
||||
- 上层再把大量短小、彼此独立的工作投递到 job system。
|
||||
|
||||
从设计意图上看,XCEngine 当前也在往这个方向走:
|
||||
|
||||
- `Mutex` 负责通用互斥,适合临界区较大或等待时间不可预测的场景。
|
||||
- `SpinLock` 适合极短临界区,代价是忙等。
|
||||
- `ReadWriteLock` 试图提升读多写少场景的吞吐量。
|
||||
- `Thread` 把 `std::thread` 的基本生命周期包装成统一接口。
|
||||
- `TaskSystem` 则意图成为更高层的统一调度器。
|
||||
|
||||
## 这些同步原语各自适合什么
|
||||
|
||||
### `Mutex`
|
||||
|
||||
它本质上是对 `std::mutex` 的轻量包装。
|
||||
|
||||
适合:
|
||||
|
||||
- 保护 map、vector、日志缓冲区等普通共享状态。
|
||||
- 需要和 `std::lock_guard`、`std::unique_lock` 等标准库工具协作的场景。
|
||||
|
||||
优点:
|
||||
|
||||
- 语义清晰。
|
||||
- 平台和标准库行为成熟。
|
||||
|
||||
代价:
|
||||
|
||||
- 阻塞时会进入内核或调度器等待路径,开销比自旋更高。
|
||||
|
||||
### `SpinLock`
|
||||
|
||||
它当前用 `atomic_flag` 做空循环忙等,没有退避、没有 `yield()`、没有公平性控制。
|
||||
|
||||
适合:
|
||||
|
||||
- 临界区极短。
|
||||
- 竞争概率低。
|
||||
- 线程数和 CPU 核数比较接近。
|
||||
|
||||
不适合:
|
||||
|
||||
- 主线程容易被卡住的路径。
|
||||
- 高竞争场景。
|
||||
- 持锁期间有任何可能阻塞的代码。
|
||||
|
||||
### `ReadWriteLock`
|
||||
|
||||
当前实现采用写者优先策略。设计动机很好理解:
|
||||
|
||||
- 对引擎里的配置表、资源索引、场景查询类结构,读远多于写时,读写锁通常比普通互斥更有吸引力。
|
||||
|
||||
但当前实现要注意:
|
||||
|
||||
- 当有写者排队时,新读者会被挡住。
|
||||
- 这会降低写饥饿风险,但提高读饥饿风险。
|
||||
|
||||
这是典型的吞吐量与公平性权衡。
|
||||
|
||||
## `Thread` 的定位是什么
|
||||
|
||||
`Thread` 当前不是线程框架,只是对 `std::thread` 的轻量包装。
|
||||
|
||||
它的价值主要在于:
|
||||
|
||||
- 统一接口风格。
|
||||
- 在对象内保存一个引擎层名称和一个缓存 ID。
|
||||
- 析构时自动 `join()`,减少“忘记回收线程句柄”的错误。
|
||||
|
||||
但这也意味着:
|
||||
|
||||
- 线程名当前不会传播到操作系统。
|
||||
- `GetId()` 与 `GetCurrentId()` 的 ID 口径并不一致。
|
||||
- 如果错误地对仍然 `joinable` 的对象再次 `Start()`,行为风险和原生 `std::thread` 一样高。
|
||||
|
||||
## `TaskSystem` 为什么现在还不能算商业级 job system
|
||||
|
||||
如果拿成熟引擎的任务系统做参照,至少会期待这些基本性质:
|
||||
|
||||
- 任务对象生命周期绝对正确。
|
||||
- 队列并发访问无数据竞争。
|
||||
- 支持明确的等待、依赖和 completion fence。
|
||||
- 关闭语义清晰。
|
||||
- 线程数、队列、分块策略和 profiling 都能受配置控制。
|
||||
|
||||
而当前源码距离这些目标还有明显差距:
|
||||
|
||||
- `Submit()` 会把裸指针压入队列,但提交函数返回时对象可能已经被销毁。
|
||||
- 任务队列的入队和出队没有统一使用同一把锁。
|
||||
- `Wait()` 是空实现。
|
||||
- `TaskGroup` 的依赖、完成计数、完成回调都没有真正接上。
|
||||
- `ParallelFor()` 不等待结果,只是批量投递。
|
||||
- `Shutdown()` 只停线程,不真正清理任务生命周期。
|
||||
|
||||
这不是文档措辞问题,而是正确性问题。对于并发系统来说,正确性比 API 漂不漂亮重要得多。
|
||||
|
||||
## 那为什么还要保留这些接口
|
||||
|
||||
因为这套接口形状本身是有价值的,它表达了引擎未来很可能需要的能力:
|
||||
|
||||
- 批处理任务提交。
|
||||
- 主线程回投。
|
||||
- 任务组与依赖图。
|
||||
- 并行 for。
|
||||
- 可扩展的调度配置。
|
||||
|
||||
很多商业引擎也是先稳定接口轮廓,再逐步把执行器、profiling、依赖图和取消语义补齐。当前文档保留这些页,不是为了假装能力已经可用,而是为了把“设计目标”和“当前现实”同时讲清楚。
|
||||
|
||||
## 当前阶段的推荐实践
|
||||
|
||||
如果你现在要在 XCEngine 里写可靠并发代码,更稳妥的做法是:
|
||||
|
||||
1. 基础同步优先用 [Mutex](../../XCEngine/Threading/Mutex/Mutex.md) 或 [ReadWriteLock](../../XCEngine/Threading/ReadWriteLock/ReadWriteLock.md)。
|
||||
2. 极短临界区再考虑 [SpinLock](../../XCEngine/Threading/SpinLock/SpinLock.md)。
|
||||
3. 需要明确线程生命周期的后台服务,优先直接用 [Thread](../../XCEngine/Threading/Thread/Thread.md)。
|
||||
4. 当前把 [TaskSystem](../../XCEngine/Threading/TaskSystem/TaskSystem.md) 和 [TaskGroup](../../XCEngine/Threading/TaskGroup/TaskGroup.md) 视为实验性骨架,而不是生产级调度器。
|
||||
|
||||
## 如果要把它演进到商业级,需要补什么
|
||||
|
||||
优先级应该是:
|
||||
|
||||
1. 修正任务所有权和引用计数协议。
|
||||
2. 修正队列同步,消除数据竞争。
|
||||
3. 实现真实的任务完成等待。
|
||||
4. 让 `TaskGroup` 的依赖、进度和完成回调真正接入调度器。
|
||||
5. 明确 `Shutdown()` / reinitialize / cancel 的状态机。
|
||||
6. 最后再补 profiling、work stealing、局部队列和更智能的 `ParallelFor`。
|
||||
|
||||
顺序不能反。商业级任务系统最先要站稳的是正确性,其次才是性能和功能丰富度。
|
||||
|
||||
## 相关 API
|
||||
|
||||
- [Threading](../../XCEngine/Threading/Threading.md)
|
||||
- [Mutex](../../XCEngine/Threading/Mutex/Mutex.md)
|
||||
- [SpinLock](../../XCEngine/Threading/SpinLock/SpinLock.md)
|
||||
- [ReadWriteLock](../../XCEngine/Threading/ReadWriteLock/ReadWriteLock.md)
|
||||
- [Thread](../../XCEngine/Threading/Thread/Thread.md)
|
||||
- [Task](../../XCEngine/Threading/Task/Task.md)
|
||||
- [TaskGroup](../../XCEngine/Threading/TaskGroup/TaskGroup.md)
|
||||
- [TaskSystem](../../XCEngine/Threading/TaskSystem/TaskSystem.md)
|
||||
Reference in New Issue
Block a user