📋 摘要
  • 推理服务收到请求后不逐个送 GPU,而是聚成 batch,靠满批立即触发与最早请求等待达 max_wait_ms 超时触发两条规则切出 batch。
  • 核心难点:策略与线程/时钟解耦。纯策略引擎(BatchEngine)只回答「给定时间哪些请求该成批」,可确定性单测;真实时钟、线程、锁只在 RealtimeBatchScheduler 适配层。
  • 本文给出三种演进版本:常规回答(单类调度器)→ 加定时器轮询 → 分层设计版(数据 / 纯策略引擎 / 真实时钟适配器)。

背景

来自面试的一个问题。题目:实现推理服务的动态批处理调度器——请求以 submit(req) 到达,按「满批立即触发 + 最早请求等待 max_wait_ms 超时触发」两条规则聚合输出 batch。

常规回答

C++
/**
题目:推理请求动态批处理调度器(简化版)

**题目描述**:实现一个批处理调度器,用于推理服务收包后聚合请求成 batch 送 GPU:
- 请求以 `submit(req)` 不断到达,`req` 含 `id` 和 `arrive_time_ms`。
- 调度器输出 batch 列表(一个 batch 是请求 id 的数组),触发规则(到达即触发 + 动态超时):
  1. 一旦队列中累积 `max_batch_size` 个请求,**立即**切出一个 batch 输出;
  2. 否则,当**最早未成批请求**的等待时间达到 `max_wait_ms` 时,把当前已到达的所有请求(≥1 个)切成一个 batch 输出;
- 提供接口:`submit(req)` 和 `poll_batches(now_ms)`(或在 submit 内部判断,二选一均可),`now_ms` 由外部时钟驱动。
**输入输出**:给一组 `(arrive_time_ms, id)` 序列和参数 `max_batch_size=4, max_wait_ms=50`,输出每次 batch 的
`(fire_time_ms, [ids])`。样例:到达时刻 0,10,20,100,110,200 → batch1@50:[0,1,2](id0 等到 50ms 超时,仅 3 个未满批)、
batch2@150:[3,4](id3 等到 150ms 超时)、batch3@250:[5](id5 单独成批)。
*/

#include <cstdint>
#include <deque>
#include <iostream>
#include <vector>

// 单个请求。
struct Request {
    int id;
    std::int64_t arrive_time_ms;
};

struct Batch {
    std::int64_t fire_time_ms;  // 触发时间
    std::vector<int> ids;
};

// 只负责调度,实际的定时器触发由外部时钟驱动。
// Note:外部来保证请求的 arrive_time_ms 是单调递增的,外部时钟驱动 poll_batches(now_ms) 也是单调递增的。
class DynamicBatchScheduler {
public:
    DynamicBatchScheduler(std::size_t max_batch_size, std::int64_t max_wait_ms)
        : max_batch_size_(max_batch_size),
          max_wait_ms_(max_wait_ms),
          current_time_ms_(0) {
        // 满批至少 1 个请求,避免切出空 batch。
        if (max_batch_size_ <= 0) {
            max_batch_size_ = 1;
        }
    }

    // 提交一个请求到调度器。外部保证时间单调递增。只负责提交和触发 batch。
    // 按照 req 时间,超时和满 batch 都会触发规则组 batch
    void submit(const Request& req) {
        if (req.arrive_time_ms < current_time_ms_) {
            std::cerr << "Request arrive_time_ms is earlier than current_time_ms_" << std::endl;
            return;
        }

        // 外部时钟推进到该请求的到达时刻,先冲刷已超时的请求(规则 2)。
        current_time_ms_ = req.arrive_time_ms;
        updateTimeoutReq();

        // 入队后若满批,立即切出一个 batch(规则 1)。
        queue_.push_back(req);
        if (queue_.size() >= max_batch_size_) {
            batches_.push_back(createBatch(req.arrive_time_ms, max_batch_size_));
        }
    }

    // 外部时钟推进到 now_ms,冲刷所有已超时的请求(规则 2),
    // 并把当前已完成、待取走的 batch 取出并清空后返回(drain 语义)。
    std::vector<Batch> poll_batches(std::int64_t now_ms) {
        current_time_ms_ = now_ms;
        updateTimeoutReq();

        std::vector<Batch> out;
        out.swap(batches_);
        return out;
    }

private:
    // 规则 2:从队首开始,把等待时间已达 max_wait_ms 的请求连续切出。
    void updateTimeoutReq() {
        while (!queue_.empty()) {
            const std::int64_t earliest = queue_.front().arrive_time_ms;
            if (current_time_ms_ < earliest + max_wait_ms_) {
                break;
            }
            // 超时触发:当前已到达的所有请求(≥1)一起切出。
            batches_.push_back(createBatch(earliest + max_wait_ms_, queue_.size()));
        }
    }

    // 从队首切出 count 个请求作为一个 batch。
    Batch createBatch(std::int64_t fire_time_ms, std::size_t count) {
        Batch b;
        b.fire_time_ms = fire_time_ms;
        for (std::size_t i = 0; i < count && !queue_.empty(); ++i) {
            b.ids.push_back(queue_.front().id);
            queue_.pop_front();
        }
        return b;
    }

    std::size_t max_batch_size_;
    std::int64_t max_wait_ms_;
    std::int64_t current_time_ms_;

    std::deque<Request> queue_;
    std::vector<Batch> batches_;
};

// 跑一个用例:提交所有请求,再把时钟推进到 final_now_ms 冲刷残余,打印结果。
static void run_case(const char* name,
                     std::size_t max_batch_size,
                     std::int64_t max_wait_ms,
                     const std::vector<Request>& requests,
                     std::int64_t final_now_ms) {
    DynamicBatchScheduler sched(max_batch_size, max_wait_ms);
    for (const Request& r : requests) {
        sched.submit(r);
    }
    const std::vector<Batch> batches = sched.poll_batches(final_now_ms);

    std::cout << name << ":\n";
    for (const Batch& b : batches) {
        std::cout << "  batch@" << b.fire_time_ms << ": [";
        for (std::size_t i = 0; i < b.ids.size(); ++i) {
            if (i != 0) std::cout << ",";
            std::cout << b.ids[i];
        }
        std::cout << "]\n";
    }
}

int main() {
    // 样例:到达时刻 0,10,20,100,110,200。
    run_case("case_timeout", 4, 50, {{0, 0}, {1, 10}, {2, 20}, {3, 100}, {4, 110}, {5, 200}}, 200);

    // 满批用例:到达时刻 0,10,20,30,凑满 4 个立即触发(规则 1)。
    run_case("case_full", 4, 50, {{0, 0}, {1, 10}, {2, 20}, {3, 30}}, 30);

    return 0;
}

增加一个定时器来调度

C
class RealtimeBatchScheduler {
public:
    using Callback = std::function<void(const Batch&)>;

    RealtimeBatchScheduler(std::size_t max_batch_size, std::int64_t max_wait_ms,
                           std::chrono::milliseconds poll_interval = std::chrono::milliseconds(5))
        : core_(max_batch_size, max_wait_ms),
          poll_interval_(poll_interval),
          stop_(false) {
        timer_ = std::thread(&RealtimeBatchScheduler::run, this);
    }

    ~RealtimeBatchScheduler() { stop(); }

    RealtimeBatchScheduler(const RealtimeBatchScheduler&) = delete;
    RealtimeBatchScheduler& operator=(const RealtimeBatchScheduler&) = delete;

    void set_callback(Callback cb) {
        std::lock_guard<std::mutex> lk(mtx_);
        callback_ = std::move(cb);
    }

    // 请求到达:盖真实时间戳 → 交给 core → 立即 drain(满批/已到期超时)并回调。
    void submit(const Request& req) {
        std::vector<Batch> fired;
        {
            std::lock_guard<std::mutex> lk(mtx_);
            Request r = req;
            r.arrive_time_ms = now_ms();
            core_.submit(r);
            fired = core_.poll_batches(r.arrive_time_ms);
        }
        for (const Batch& b : fired) {
            deliver(b);
        }
    }

    void stop() {
        {
            std::lock_guard<std::mutex> lk(mtx_);
            if (stop_) {
                return;
            }
            stop_ = true;
        }
        cv_.notify_all();
        if (timer_.joinable()) {
            timer_.join();
        }
    }

private:
    using Clock = std::chrono::steady_clock;

    static std::int64_t now_ms() {
        return std::chrono::duration_cast<std::chrono::milliseconds>(
                   Clock::now().time_since_epoch())
            .count();
    }

    // 周期轮询:每隔 poll_interval_ 推进一次时钟,drain 超时 batch。
    void run() {
        std::unique_lock<std::mutex> lk(mtx_);
        while (true) {
            if (stop_) {
                return;
            }
            if (cv_.wait_for(lk, poll_interval_, [this] { return stop_; })) {
                return;  // 被 stop 唤醒
            }
            std::vector<Batch> fired = core_.poll_batches(now_ms());
            lk.unlock();
            for (const Batch& b : fired) {
                deliver(b);
            }
            lk.lock();
        }
    }

    // 先拷贝回调再在锁外调用,避免回调里再进 submit 死锁。
    void deliver(const Batch& b) {
        Callback cb;
        {
            std::lock_guard<std::mutex> lk(mtx_);
            cb = callback_;
        }
        if (cb) {
            cb(b);
        }
    }

    DynamicBatchScheduler core_;
    std::chrono::milliseconds poll_interval_;
    bool stop_;

    std::mutex mtx_;
    std::condition_variable cv_;
    Callback callback_;
    std::thread timer_;
};

// 真实定时器演示:稀疏到达触发超时,密集到达触发满批。
static void demo_realtime() {
    const auto start = std::chrono::steady_clock::now();
    const std::int64_t start_ms =
        std::chrono::duration_cast<std::chrono::milliseconds>(start.time_since_epoch()).count();

    RealtimeBatchScheduler sched(/*max_batch_size=*/4, /*max_wait_ms=*/50);
    sched.set_callback([start_ms](const Batch& b) {
        std::cout << "  batch@" << (b.fire_time_ms - start_ms) << ": [";
        for (std::size_t i = 0; i < b.ids.size(); ++i) {
            if (i != 0) std::cout << ",";
            std::cout << b.ids[i];
        }
        std::cout << "]\n";
    });

    std::cout << "realtime:\n";

    // 稀疏:3 个(不满批),id0 会等到 50ms 超时被切出。
    sched.submit({0, 0});
    std::this_thread::sleep_for(std::chrono::milliseconds(10));
    sched.submit({1, 0});
    std::this_thread::sleep_for(std::chrono::milliseconds(10));
    sched.submit({2, 0});
    std::this_thread::sleep_for(std::chrono::milliseconds(100));  // 等超时触发

    // 密集:连发 4 个,凑满立即触发。
    sched.submit({3, 0});
    sched.submit({4, 0});
    sched.submit({5, 0});
    sched.submit({6, 0});
    std::this_thread::sleep_for(std::chrono::milliseconds(100));  // 等剩余超时触发

    sched.stop();
}

更新的版本,更加贴近真实环境

C++
/**
题目:推理请求动态批处理调度器 —— 三层设计版(真实定时器)

分层(从 0 开始的推荐结构):
1. Request / Batch —— 纯数据。
2. BatchEngine —— 纯策略引擎(组 batch 规则),无线程、无真实时钟、无锁。
   只回答"给定当前时间,哪些请求该成批"。submit / advance_to 直接返回触发的 batch。
3. RealtimeBatchScheduler —— 真实时钟适配器,唯一碰线程/锁/真实时钟的地方。
   持有一个 BatchEngine,起一个线程用 condition_variable 等到队首 deadline 自动触发超时。

用法:
- 确定性测试:直接驱动 BatchEngine(submit/advance_to 传显式时间),零抖动。
- 生产:用 RealtimeBatchScheduler,submit 到达 + set_callback 收 batch。
*/

#include <chrono>
#include <condition_variable>
#include <cstdint>
#include <deque>
#include <functional>
#include <iostream>
#include <mutex>
#include <thread>
#include <vector>

// ===== 第 1 层:纯数据 =====
struct Request {
    int id;
    std::int64_t arrive_time_ms;
};

struct Batch {
    std::int64_t fire_time_ms;  // 触发时间
    std::vector<int> ids;
};

// ===== 第 2 层:纯策略引擎 =====
// 单线程、无真实时钟、无 I/O。组 batch 的规则只在这里有一份。
class BatchEngine {
public:
    BatchEngine(std::size_t max_batch_size, std::int64_t max_wait_ms)
        : max_batch_size_(max_batch_size ? max_batch_size : 1),
          max_wait_ms_(max_wait_ms),
          current_time_ms_(0) {}

    // 到达一个请求(按到达时间顺序调用)。返回因此触发的 batch(超时 + 满批)。
    std::vector<Batch> submit(const Request& req) {
        if (req.arrive_time_ms < current_time_ms_) {
            std::cerr << "arrive_time_ms is earlier than current_time_ms_" << std::endl;
            return {};
        }

        // 外部时钟推进到该请求的到达时刻,先冲刷已超时的请求(规则 2)。
        current_time_ms_ = req.arrive_time_ms;
        std::vector<Batch> fired = flushTimeouts();

        // 入队后若满批,立即切出一个 batch(规则 1)。
        queue_.push_back(req);
        if (queue_.size() >= max_batch_size_) {
            fired.push_back(cut(req.arrive_time_ms, max_batch_size_));
        }
        return fired;
    }

    // 推进时钟到 now_ms,返回因此触发的超时 batch。
    std::vector<Batch> advance_to(std::int64_t now_ms) {
        current_time_ms_ = now_ms;
        return flushTimeouts();
    }

    // 队首请求的 deadline(到达 + max_wait);空队列返回 -1。
    std::int64_t next_deadline_ms() const {
        return queue_.empty() ? -1 : queue_.front().arrive_time_ms + max_wait_ms_;
    }

private:
    // 规则 2:最早请求等待达 max_wait_ms,把当前所有请求一起切出。
    std::vector<Batch> flushTimeouts() {
        std::vector<Batch> fired;
        while (!queue_.empty()) {
            const std::int64_t earliest = queue_.front().arrive_time_ms;
            if (current_time_ms_ < earliest + max_wait_ms_) {
                break;
            }
            fired.push_back(cut(earliest + max_wait_ms_, queue_.size()));
        }
        return fired;
    }

    // 从队首切出 count 个请求为一个 batch。
    Batch cut(std::int64_t fire_time_ms, std::size_t count) {
        Batch b;
        b.fire_time_ms = fire_time_ms;
        for (std::size_t i = 0; i < count && !queue_.empty(); ++i) {
            b.ids.push_back(queue_.front().id);
            queue_.pop_front();
        }
        return b;
    }

    std::size_t max_batch_size_;
    std::int64_t max_wait_ms_;
    std::int64_t current_time_ms_;
    std::deque<Request> queue_;
};

// ===== 第 3 层:真实时钟适配器 =====
// 引擎由一把 mutex 串行化所有访问(引擎本身不加锁)。定时线程用 condition_variable
// 等到队首 deadline,到点推进引擎并回调超时 batch;满批在 submit 时立即回调。
class RealtimeBatchScheduler {
public:
    using Callback = std::function<void(const Batch&)>;

    RealtimeBatchScheduler(std::size_t max_batch_size, std::int64_t max_wait_ms)
        : engine_(max_batch_size, max_wait_ms), stop_(false) {
        timer_ = std::thread(&RealtimeBatchScheduler::run, this);
    }

    ~RealtimeBatchScheduler() { stop(); }

    RealtimeBatchScheduler(const RealtimeBatchScheduler&) = delete;
    RealtimeBatchScheduler& operator=(const RealtimeBatchScheduler&) = delete;

    void set_callback(Callback cb) {
        std::lock_guard<std::mutex> lk(mtx_);
        callback_ = std::move(cb);
    }

    // 请求到达:盖真实时间戳,交给引擎,触发的 batch 通过回调交付。
    void submit(const Request& req) {
        std::vector<Batch> fired;
        {
            std::lock_guard<std::mutex> lk(mtx_);
            Request r = req;
            r.arrive_time_ms = now_ms();
            fired = engine_.submit(r);
        }
        for (const Batch& b : fired) {
            deliver(b);
        }
        cv_.notify_all();  // 唤醒定时线程重算 deadline
    }

    void stop() {
        {
            std::lock_guard<std::mutex> lk(mtx_);
            if (stop_) {
                return;
            }
            stop_ = true;
        }
        cv_.notify_all();
        if (timer_.joinable()) {
            timer_.join();
        }
    }

private:
    using Clock = std::chrono::steady_clock;
    using TimePoint = Clock::time_point;

    static std::int64_t now_ms() {
        return std::chrono::duration_cast<std::chrono::milliseconds>(
                   Clock::now().time_since_epoch())
            .count();
    }

    static TimePoint from_ms(std::int64_t ms) {
        return TimePoint(std::chrono::milliseconds(ms));
    }

    // 定时线程:等到队首 deadline,到点推进引擎并回调超时 batch。
    void run() {
        std::unique_lock<std::mutex> lk(mtx_);
        while (true) {
            if (stop_) {
                return;
            }

            const std::int64_t deadline = engine_.next_deadline_ms();
            if (deadline < 0) {  // 空队列
                cv_.wait(lk, [this] { return stop_ || engine_.next_deadline_ms() >= 0; });
                continue;
            }

            // 等到 deadline;若期间队首变化 / 停止,则提前醒来重算。
            const bool timed_out = !cv_.wait_until(lk, from_ms(deadline), [this, deadline] {
                return stop_ || engine_.next_deadline_ms() != deadline;
            });

            if (stop_) {
                return;
            }
            if (timed_out) {
                std::vector<Batch> fired = engine_.advance_to(now_ms());
                lk.unlock();
                for (const Batch& b : fired) {
                    deliver(b);
                }
                lk.lock();
            }
        }
    }

    // 先拷贝回调再在锁外调用,避免回调里再进 submit 死锁。
    void deliver(const Batch& b) {
        Callback cb;
        {
            std::lock_guard<std::mutex> lk(mtx_);
            cb = callback_;
        }
        if (cb) {
            cb(b);
        }
    }

    BatchEngine engine_;
    bool stop_;

    std::mutex mtx_;
    std::condition_variable cv_;
    Callback callback_;
    std::thread timer_;
};

// 打印一批 batch。
static void print_batches(const char* name, const std::vector<Batch>& batches) {
    std::cout << name << ":\n";
    for (const Batch& b : batches) {
        std::cout << "  batch@" << b.fire_time_ms << ": [";
        for (std::size_t i = 0; i < b.ids.size(); ++i) {
            if (i != 0) std::cout << ",";
            std::cout << b.ids[i];
        }
        std::cout << "]\n";
    }
}

int main() {
    // ===== 确定性:直接驱动引擎(零线程、零抖动,可做单测) =====
    {
        BatchEngine engine(/*max_batch_size=*/4, /*max_wait_ms=*/50);
        const std::vector<Request> reqs = {
            {0, 0}, {1, 10}, {2, 20}, {3, 100}, {4, 110}, {5, 200},
        };
        std::vector<Batch> all;
        for (const Request& r : reqs) {
            std::vector<Batch> f = engine.submit(r);
            all.insert(all.end(), f.begin(), f.end());
        }
        std::vector<Batch> tail = engine.advance_to(/*now_ms=*/250);
        all.insert(all.end(), tail.begin(), tail.end());
        print_batches("engine_timeout", all);
    }
    {
        BatchEngine engine(/*max_batch_size=*/4, /*max_wait_ms=*/50);
        std::vector<Batch> all;
        for (const Request& r : std::vector<Request>{{0, 0}, {1, 10}, {2, 20}, {3, 30}}) {
            std::vector<Batch> f = engine.submit(r);
            all.insert(all.end(), f.begin(), f.end());
        }
        print_batches("engine_full", all);
    }

    // ===== 真实定时器:自动触发超时 + 满批 =====
    {
        RealtimeBatchScheduler sched(/*max_batch_size=*/4, /*max_wait_ms=*/50);

        const auto start = std::chrono::steady_clock::now();
        auto rel_ms = [&start] {
            return std::chrono::duration_cast<std::chrono::milliseconds>(
                       std::chrono::steady_clock::now() - start)
                .count();
        };

        sched.set_callback([&rel_ms](const Batch& b) {
            std::cout << "  [" << rel_ms() << "ms] batch: [";
            for (std::size_t i = 0; i < b.ids.size(); ++i) {
                if (i != 0) std::cout << ",";
                std::cout << b.ids[i];
            }
            std::cout << "]\n";
        });

        std::cout << "realtime:\n";

        // 稀疏:3 个(不满批),id0 会等到 50ms 超时被切出。
        // 注意:真实版会盖真实时间戳,第二个字段(arrive_time_ms)用 0 占位即可。
        sched.submit({0, 0});
        std::this_thread::sleep_for(std::chrono::milliseconds(10));
        sched.submit({1, 0});
        std::this_thread::sleep_for(std::chrono::milliseconds(10));
        sched.submit({2, 0});
        std::this_thread::sleep_for(std::chrono::milliseconds(100));  // 等超时触发

        // 密集:连发 4 个,凑满立即触发。
        sched.submit({3, 0});
        sched.submit({4, 0});
        sched.submit({5, 0});
        sched.submit({6, 0});
        std::this_thread::sleep_for(std::chrono::milliseconds(100));  // 等剩余超时触发

        sched.stop();
    }
    return 0;
}