限流需实时判断而非阻塞等待,应使用滑动窗口或令牌桶算法,依赖std::chrono::steady_clock实现高精度、无锁、低开销的请求控制。

为什么不用 std::chrono 直接 sleep?
限流不是“等时间到了再放行”,而是实时判断请求是否该被拒绝。用 std::this_thread::sleep_for 会阻塞线程,吞吐量直接归零,还可能让超时请求堆积——这已经不是限流,是卡死。
真正要做的,是在每次请求进来时,基于当前时间戳快速计算窗口内请求数,不阻塞、不锁全局计数器(除非必要)。
- 推荐用滑动窗口或令牌桶,两者都依赖高精度时间:优先用
std::chrono::steady_clock(不被系统时间调整影响) - 避免用
system_clock,NTP 校时会导致时间跳变,窗口错乱 - 不要在关键路径上做
time_t或gettimeofday()调用,开销大且精度低
滑动窗口:用 std::deque 存时间戳,但要注意内存增长
每个请求记录一个 steady_clock::time_point,入队;每次检查时,把超出窗口的旧时间戳出队。窗口长度比如 1 秒,允许最多 100 次请求。
class SlidingWindowLimiter {
std::deque<:chrono::steady_clock::time_point> timestamps;
std::chrono::seconds window_;
size_t max_count_;
<p>public:
SlidingWindowLimiter(std::chrono::seconds window, size_t max<em>count)
: window</em>(window), max<em>count</em>(max_count) {}</p>
<pre class="brush:php;toolbar:false;">bool try_acquire() {
auto now = std::chrono::steady_clock::now();
// 清理过期时间戳
auto cutoff = now - window_;
while (!timestamps.empty() && timestamps.front() <p>};</p><div class="aritcle_card flexRow artxards">
<div class="artcardd flexRow">
<a class="aritcle_card_img" rel="nofollow" href="/xiazai/skill2659" title="C++"><img
src="https://img.php.cn/upload/skill/000/000/081/178927213426672.jpg" alt="C++" onerror="this.onerror='';this.src='/static/lhimages/moren/morentu.png'" ></a>
<div class="aritcle_card_info flexColumn">
<a rel="nofollow" href="/xiazai/skill2659" title="C++" class="overflowclass">C++</a>
<p class="overflowclass">"空空如也"</p>
</div>
<a rel="nofollow" href="/xiazai/skill2659" title="C++" class="aritcle_card_btn flexRow flexcenter"><b></b><span>下载</span>
</a>
</div>
</div>
- 注意:如果 QPS 极高(比如 10k+/s),
deque的频繁 push/pop 可能成为瓶颈;可改用循环数组 + 原子索引(但需预分配固定大小) - 不能用
std::vector::erase清理开头元素——O(n) 移动开销太大 - 这个实现是单线程安全的;多线程需加
std::mutex,但锁粒度大,会串行化所有请求
令牌桶:用 std::atomic<int></int> 管理剩余令牌,更轻量
令牌按固定速率生成(比如每 10ms 补 1 个),请求消耗 1 个令牌。核心是原子更新剩余令牌数,避免锁。
class TokenBucketLimiter {
std::atomic<int> tokens_;
const int capacity_;
const std::chrono::milliseconds refill_interval_;
std::chrono::steady_clock::time_point last_refill_;
<p>public:
TokenBucketLimiter(int capacity, std::chrono::milliseconds interval)
: tokens<em>(capacity), capacity</em>(capacity), refill<em>interval</em>(interval),
last<em>refill</em>(std::chrono::steady_clock::now()) {}</p>
<pre class="brush:php;toolbar:false;">bool try_acquire() {
auto now = std::chrono::steady_clock::now();
auto elapsed = std::chrono::duration_cast<:chrono::milliseconds>(now - last_refill_);
int new_tokens = static_cast<int>(elapsed.count() / refill_interval_.count());
if (new_tokens > 0) {
// 原子地更新 last_refill_ 和 tokens_
last_refill_ = now - std::chrono::milliseconds(
(elapsed.count() % refill_interval_.count()));
int expected = tokens_.load();
do {
int desired = std::min(capacity_, expected + new_tokens);
if (tokens_.compare_exchange_weak(expected, desired)) break;
} while (true);
}
return tokens_.fetch_sub(1) > 0;
}</int></:chrono::milliseconds>
};
- 注意:上面的
last_refill_更新不是完全精确(因取整),但误差可控;若要求严格线性补发,得用浮点累积 + 原子 double(C++20 支持std::atomic<double></double>,否则需自旋+整数模拟) -
fetch_sub返回的是减之前的值,所以判断> 0才代表“还有令牌可扣” - 这个版本无锁,适合高并发;但要注意
capacity_别设太大,否则tokens_可能溢出(不过 int 通常够用)
生产环境必须考虑的三个细节
算法本身只是骨架,落地时这几个点常被忽略,却直接决定是否可用:
- 时钟源必须稳定:
steady_clock在某些嵌入式或虚拟机里可能退化为system_clock,上线前用steady_clock::is_steady断言校验 - 不要把限流逻辑写进 hot path 的锁区内——比如在某个业务 mutex 里调用
try_acquire(),等于把整个业务串行化 - 限流失败时,返回明确错误码(如 HTTP 429)并带
Retry-After头;但头里的秒数别硬算“下次令牌生成时间”,建议统一返回固定值(如 1),避免暴露内部调度细节
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!










