📘 本篇目标是:用通俗易懂的方式,从零构建一个支持线程池调度的事件中心 SignalHub,结合我们前几天学到的协程与 resume 技术,打造一个结构清晰、易于学习、可用于实际工程的异步事件系统。
✅ 简单回顾:我们已经学会了什么?
技术点 | 说明 |
---|---|
协程机制 | 使用 co_await 挂起,resume 恢复执行 |
Awaiter 类 | 用于管理协程何时挂起、谁来恢复 |
信号发射 | 使用 emit() 来触发一个事件 |
调度器接口 | 使用 Dispatcher 控制在哪个线程运行 |
📌 今天的重点是:支持线程池的多事件中心设计,并结合协程 resume。
✅ 一、创建一个简单线程池
class ThreadPool : public Dispatcher {
public:
ThreadPool(size_t count = 2) : running(true) {
for (size_t i = 0; i < count; ++i) {
workers.emplace_back([this]() {
while (true) {
std::function<void()> task;
{
std::unique_lock<std::mutex> lock(mtx);
cv.wait(lock, [&]() { return !tasks.empty() || !running; });
if (!running && tasks.empty()) return;
task = std::move(tasks.front());
tasks.pop();
}
task();
}
});
}
}
void dispatch(std::function<void()> task) override {
{
std::lock_guard<std::mutex> lock(mtx);
tasks.push(std::move(task));
}
cv.notify_one();
}
void stop() {
running = false;
cv.notify_all();
for (auto& t : workers) t.join();
}
private:
std::vector<std::thread> workers;
std::queue<std::function<void()>> tasks;
std::mutex mtx;
std::condition_variable cv;
bool running;
};
📌 用法:用 dispatch(fn)
提交任务给线程池。
✅ 二、定义 Awaiter 类型(协程等待信号)
template<typename T>
struct HubAwaiter {
std::optional<T> result;
std::coroutine_handle<> handle;
Dispatcher* dispatcher;
bool await_ready() const noexcept { return false; }
void await_suspend(std::coroutine_handle<> h) { handle = h; }
T await_resume() { return *result; }
void resume(T value) {
result = std::move(value);
dispatcher->dispatch([h = handle]() { h.resume(); });
}
};
📌 说明:这个类负责让协程挂起,并在合适的时候 resume。
✅ 三、构建可等待信号中心 AwaitableSignalHub
template<typename Key, typename T>
class AwaitableSignalHub {
public:
AwaitableSignalHub(Dispatcher* d) : dispatcher(d) {}
void emit(const Key& key, const T& value) {
auto it = waiters.find(key);
if (it != waiters.end()) {
for (auto* w : it->second) w->resume(value);
waiters.erase(it);
}
}
HubAwaiter<T>* wait(const Key& key) {
auto* w = new HubAwaiter<T>();
w->dispatcher = dispatcher;
waiters[key].push_back(w);
return w;
}
private:
Dispatcher* dispatcher;
std::unordered_map<Key, std::vector<HubAwaiter<T>*>> waiters;
};
📌 用法:
wait("login")
:协程等待某个事件 keyemit("login", "user")
:发出事件并 resume 对应协程
✅ 四、实战案例:等待登录成功
Task<void> waitLogin(AwaitableSignalHub<std::string, std::string>& hub) {
std::string user = co_await *hub.wait("login");
std::cout << "✅ 登录成功,用户是:" << user << std::endl;
co_return;
}
int main() {
ThreadPool pool(2); // 初始化线程池(2 个线程)
AwaitableSignalHub<std::string, std::string> hub(&pool); // 构建事件中心
auto task = waitLogin(hub); // 协程开始等待登录事件
std::this_thread::sleep_for(std::chrono::milliseconds(300));
hub.emit("login", "Alice"); // 发出登录事件
std::this_thread::sleep_for(std::chrono::seconds(1));
pool.stop(); // 停止线程池
return 0;
}
📌 输出:
✅ 登录成功,用户是:Alice
✅ 今日重点总结
关键组件 | 作用 |
---|---|
ThreadPool | 处理异步任务的线程池 |
Dispatcher 接口 | 控制协程 resume 到哪个线程 |
Awaiter/HubAwaiter | 协程挂起点,封装 resume 动作 |
AwaitableSignalHub | 事件中心,支持事件挂起 + resume |
🧠 巩固小问答
Q1:协程 resume 到哪个线程?
由 Dispatcher 决定,可能是线程池线程,也可能是主线程。
Q2:事件 key 可以是什么?
任意类型(如字符串、枚举、int 等)都可以作为事件标识。
Q3:多个协程可以同时监听同一个事件吗?
可以,多个协程会各自 resume。
Q4:wait 之后不 emit 会怎样?
协程会一直挂起,直到事件被 emit。
Q5:是否适合 UI 事件驱动?
非常适合。配合主线程 Dispatcher 可实现按钮点击、输入框更新等响应。
🔭 下一步 Day 16:结合 GUI 框架构建响应式界面组件
- 如何将
SignalHub
嵌入到图形界面(如 ImGui、SDL) - 如何监听按钮点击、输入更新等 GUI 事件并响应协程
- 构建轻量响应式事件绑定机制,支持跨平台主线程 resume
📌 如果你更偏向某个实际应用方向(游戏引擎、嵌入式控制、UI 表单等),可以告诉我,我会根据你的需求来定制 👨💻