直接用sleep_for控速不靠谱,因它将网络等待时间计入限速窗口,导致吞吐波动;应改用独立运行的令牌桶,以单位时间发数据量为准,结合atomic操作、条件变量超时等待及读回调预检实现精准限速。

为什么直接用 std::this_thread::sleep_for 控制上传速率不靠谱
因为网络 I/O 本身是阻塞或异步的,单纯在每次发片后 sleep,会把「网络等待时间」也算进限速窗口,实际吞吐忽高忽低,尤其在高延迟或抖动网络下,限速完全失效。真正要控的是「单位时间内发出的数据量」,不是「发片间隔」。
- 令牌桶必须独立于 I/O 调度运行,推荐用
std::thread+std::chrono::steady_clock定期注入令牌 - 每次准备发送分片前,先向桶申请对应字节数的令牌(比如一个 64KB 分片 → 申请 65536 个令牌)
- 申请失败就等待,但要用
std::condition_variable::wait_until配合超时,避免假死 - 别用
std::mutex全局锁桶,改用std::atomic管理剩余令牌数 + CAS 操作,减少争用
libcurl 多句柄上传中如何安全嵌入令牌桶逻辑
libcurl 的 CURLM 多句柄模式本身不提供速率钩子,不能靠 CURLOPT_XFERINFOFUNCTION 反推限速——那个回调只报告已传量,无法干预发送节奏。
- 必须在
curl_easy_setopt(h, CURLOPT_READFUNCTION, ...)的自定义读回调里做令牌预检:每次被要求提供数据前,先扣令牌;扣不到就返回CURLE_AGAIN,并设置CURLOPT_TIMEOUT_MS防止卡死 - 注意
CURLOPT_UPLOAD必须设为1L,且CURLOPT_INFILESIZE_LARGE要准确填分片大小,否则 libcurl 可能提前结束上传 - 多个分片共用一个令牌桶时,桶对象需声明为
static或全局,但访问必须线程安全(见上条 atomic 建议)
分片上传失败后,令牌要不要退还?
要退,而且必须在收到明确失败响应(如 HTTP 503、连接断开、libcurl 返回非 CURLE_OK)后立刻退还,否则后续分片可能因令牌不足而饿死。
- 不要等整个批量任务结束再统一退,网络错误是局部的
- 退还数量 = 该分片申请的字节数(不是已发字节数),因为限速依据是「计划发送量」
- 如果失败发生在传输中途(比如发了 32KB 后断连),仍按整片大小退还——令牌桶不追踪已发进度,只管准入控制
- 用
std::atomic_fetch_add_explicit做无锁退还,避免和注入线程竞争
Windows 下 WSAEventSelect 和令牌桶怎么协同?
如果你用 Winsock 事件模型驱动上传(而非 libcurl),不能让事件循环被令牌桶阻塞。事件句柄就绪 ≠ 立即能发数据,还得看桶里有没有令牌。
立即学习“C++免费学习笔记(深入)”;
- 注册 socket 时,只监听
FD_WRITE,但每次WSAEnumNetworkEvents收到就绪通知后,先查令牌桶;没令牌就忽略本次就绪,继续等待下一次 - 绝不能在
WSAWaitForMultipleEvents中加入令牌桶的 waitable handle——Windows 没有「原子等待事件+条件变量」的原生组合 API - 更稳的做法:把令牌桶的「令牌可用」信号转成一个人工
WSAEVENT,用WSASetEvent触发,并在主事件循环里一并等待它和 socket 事件










