C++每日训练 Day 14:线程调度下的异步信号系统设计

📘 本篇目标是:在理解协程信号机制基础上,加入线程调度能力,实现一个支持异步 resume 到特定线程(如主线程或线程池)的 Signal/Slot 系统。内容尽量降低门槛,所有示例都附详细注释,配合常见问题解析帮助你一步步构建清晰的异步事件处理系统。


🔁 Day 13 回顾:协程驱动的信号机制

内容点说明
SignalAwaiter信号挂起点,保存 coroutine_handle,emit 后 resume 执行
Task 协程封装提供简单 promise_type,支持 return_value 与 result 获取
AsyncSignal支持 emit 触发协程 resume,多个协程同时等待

📌 本篇将在结构中加入线程调度能力,让 emit 可以 resume 协程到 指定线程或线程池执行


🎯 今日目标:让信号 resume 协程时运行在指定线程中

功能模块作用说明
调度器接口 Dispatcher接口类,定义 dispatch 到指定线程的能力
ThreadDispatcher实现简单线程队列调度
AwaiterWithDispatcherresume 协程时先交给调度器派发执行
应用案例在信号 emit 后 resume 到主线程

✅ 一、构建线程调度器接口

class Dispatcher {
public:
    virtual void dispatch(std::function<void()> task) = 0;
    virtual ~Dispatcher() = default;
};

// 📌 这是一个调度器接口,任何线程执行者都可以实现它。


✅ 二、构建主线程/单线程任务队列

#include <queue>
#include <mutex>
#include <condition_variable>
#include <thread>

class ThreadDispatcher : public Dispatcher {
public:
    ThreadDispatcher() : running(true), worker([this](){ this->run(); }) {}

    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();
        worker.join();
    }

private:
    std::queue<std::function<void()>> tasks;
    std::mutex mtx;
    std::condition_variable cv;
    bool running;
    std::thread worker;

    void run() {
        while (running) {
            std::function<void()> task;
            {
                std::unique_lock<std::mutex> lock(mtx);
                cv.wait(lock, [this]() { return !tasks.empty() || !running; });
                if (!running && tasks.empty()) break;
                task = std::move(tasks.front());
                tasks.pop();
            }
            task(); // ✅ 执行任务
        }
    }
};

// 📌 使用 dispatch() 可向此线程提交任务。


✅ 三、构建 AwaiterWithDispatcher

#include <optional>
#include <coroutine>

template<typename T>
struct AwaiterWithDispatcher {
    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([handle = handle]() { handle.resume(); }); // 📌 在调度器上 resume
    }
};

// 📌 使用调度器 resume 协程,resume 的动作将转移到 dispatcher 所在线程。


✅ 四、封装 AsyncSignalWithDispatcher

template<typename T>
class AsyncSignalWithDispatcher {
public:
    explicit AsyncSignalWithDispatcher(Dispatcher* d) : dispatcher(d) {}

    void emit(const T& value) {
        for (auto* waiter : waiters) {
            waiter->resume(value);
        }
        waiters.clear();
    }

    AwaiterWithDispatcher<T>* wait() {
        auto* w = new AwaiterWithDispatcher<T>();
        w->dispatcher = dispatcher;
        waiters.push_back(w);
        return w;
    }

private:
    Dispatcher* dispatcher;
    std::vector<AwaiterWithDispatcher<T>*> waiters;
};

// 📌 信号将 resume 协程到 dispatcher 所在线程执行。


✅ 五、完整实战:主线程 resume 协程等待信号

Task<void> waitInMainThread(AsyncSignalWithDispatcher<std::string>& sig) {
    std::string v = co_await *sig.wait();
    std::cout << "✅ 协程 resume 于主线程,值是:" << v << std::endl;
    co_return;
}

主函数:

int main() {
    ThreadDispatcher mainThreadDispatcher;
    AsyncSignalWithDispatcher<std::string> sig(&mainThreadDispatcher);

    auto task = waitInMainThread(sig);

    std::thread t([&]() {
        std::this_thread::sleep_for(std::chrono::milliseconds(500));
        sig.emit("signal_from_worker");
    });

    std::this_thread::sleep_for(std::chrono::seconds(2));
    mainThreadDispatcher.stop();
    t.join();
    return 0;
}

📌 说明:

  • emit 在 worker 线程调用
  • 协程 resume 到 mainThreadDispatcher 所在线程(即主线程)

输出示例:

✅ 协程 resume 于主线程,值是:signal_from_worker

✅ 六、总结与通俗理解

功能你应该记住的关键点
resume 到主线程用 dispatch 包住 resume 调用
使用线程调度器提交任务到你想要 resume 的线程
协程挂起点安全封装用 awaiter 保存 coroutine_handle,再 resume
emit 不等于执行emit 只负责通知 + 派发,真正 resume 是由 dispatcher 决定

📌 小结:事件发生 ≠ 马上执行代码事件发生 + 指定线程 resume 才是现代异步架构!
在这里插入图片描述


📘 学习巩固:问题与答案

Q1:dispatcher 是做什么的?

A:dispatcher 是任务调度器,负责将 resume 协程的任务交给特定线程处理。

Q2:为什么 emit 不直接 resume?

A:直接 resume 会在当前线程运行,但我们常需要 resume 到主线程/UI线程或线程池。

Q3:协程 await 时在做什么?

A:await 会暂停协程执行,把控制权交给 dispatcher,直到 signal emit 再 resume。

Q4:多个协程等待 signal 会怎么样?

A:所有 waiter 都会被触发,各自 resume 到各自 dispatcher。

Q5:是否可以结合线程池?

A:完全可以,只要线程池实现了 Dispatcher 接口,就可以作为调度器。


🔭 下一站 Day 15 预告:结合线程池、事件队列,构建高性能信号调度中心

我们将实现:

  • 多线程下统一信号中心
  • 线程池处理 resume 协程
  • 带事件 ID 的泛型调度器 + 动态订阅系统

📌 如果你有特定实战需求(如跨线程 UI 更新、网络 IO 回调合流等),欢迎留言告诉我 ✅

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值