令牌桶核心用std::atomic管理令牌数,按需计算补发(rate×时间差),compare_exchange_weak循环重试扣减;支持try_acquire(非阻塞)和acquire(指数退避等待);rate/max_tokens用double防低频失真,时钟回跳、溢出、零容量需严格防护。

令牌桶核心逻辑怎么用 C++ 实现
手写令牌桶,关键不是堆类,而是守住两个动作:定时补充令牌、请求时原子扣减。C++ 里最直接的方式是用 std::atomic 管理当前令牌数,配合单调时钟(std::chrono::steady_clock)计算间隔时间,避免系统时间跳变导致误补。
不推荐用 std::mutex + 普通 int,高并发下锁争用会成为瓶颈;也不建议依赖 sleep 定时线程去“主动加令牌”,既难精确又增加线程开销。真正的手写,是“按需计算”——每次请求来时,先算从上次到现在该加多少,再尝试扣减。
-
tokens用std::atomic<int64_t></int64_t>,支持负值(表示欠额),便于实现“预支+阻塞等待”语义 - 补令牌公式:新增量 =
rate * (now - last_refill_time) / 1e9(单位转为秒),用std::chrono::nanoseconds避免浮点误差累积 - 必须用
compare_exchange_weak原子更新,防止 ABA 问题;失败就重试,而不是直接返回 false
如何支持阻塞等待(wait)和非阻塞尝试(try_acquire)
很多场景需要区分“等得及”和“等不及”:try_acquire 适合 API 网关快速拒绝;acquire(带超时)适合后台任务调度。C++ 没有内置的条件变量式令牌等待,所以得自己做“轮询+休眠”或结合 std::condition_variable,但后者要加锁,破坏无锁初衷。
更轻量的做法是:在 acquire 中循环调用 try_acquire,若失败且还有剩余等待时间,用 std::this_thread::sleep_for 小步退避(比如 100μs 起步,指数退避到 1ms)。这样不引入锁,也避免忙等耗尽 CPU。
立即学习“C++免费学习笔记(深入)”;
-
try_acquire(int count = 1):只做一次原子计算和扣减,返回bool -
acquire(int count, std::chrono::nanoseconds timeout):内部循环,每次 sleep 时间随等待延长递增 - 注意:timeout 是总上限,不是每次 sleep 的时长;休眠前必须重新计算当前可用令牌,否则可能漏掉其他线程刚补充的量
为什么 max_tokens 和 rate 要用 double 表示
限流参数如 “100 QPS” 或 “500 tokens/sec” 在代码里如果存成 int,会导致低频场景(比如 0.5 token/sec)根本无法表达。C++ 手写令牌桶必须接受浮点 rate,因为补令牌本质是连续过程,离散整数累加会严重偏移预期速率。
实际存储中,rate 用 double,但所有原子操作仍基于 int64_t 令牌计数——补令牌时把 delta_sec * rate 向下取整(floor),避免因浮点误差多发令牌;扣减永远按整数进行。
- 不要用
int rate_per_sec+ 每秒固定加一次,这会放大抖动,尤其在 burst 场景下表现差 -
max_tokens可以是int,但初始化时建议也用double接口,保持语义一致(比如允许传10.5) - 测试时重点验证:持续请求下长期速率是否趋近
rate,突发请求能否利用max_tokens缓冲
常见 crash 和线程安全陷阱
最常崩在三个地方:时钟回跳导致补大量负令牌、compare_exchange_weak 忘了写重试循环、max_tokens 为 0 时未禁止补令牌。这些都不是理论问题,是上线后真实发生的 SIGFPE 或无限循环。
- 每次读
last_refill_time后,立刻用std::max(now, last_refill_time)截断,防止时钟校正造成delta为负 -
compare_exchange_weak必须包在 while 循环里,裸用一次就返回是典型错误写法 - 当
max_tokens == 0,应直接禁止所有请求(返回 false),而不是让令牌数无限下降——否则int64_t溢出后行为未定义 - 别在构造函数里启动后台线程去 refill;所有状态变更必须由请求线程驱动,才能保证可测试性和无锁性
真正难的不是写对单次逻辑,而是在纳秒级时钟、整数溢出、浮点截断、ABA、时钟跳变这些边界上全都不出错。手写令牌桶,80% 的工作量都在防御这些细节。


















