必须使用带容量上限的线程安全队列实现流量控制:std::queue配std::mutex和std::condition_variable,满时生产者阻塞或丢弃;避免std::deque;容量建议1024~4096;高吞吐场景可用std::atomic双检优化。

用 std::queue + std::mutex + std::condition_variable 做基础流量控制
直接裸用 std::thread 启一堆发送线程,不加节制地 push 数据,接收端或网络栈一卡,内存就爆。必须在生产者(数据源)和消费者(发送线程)之间插一层带容量上限的队列。
关键不是“异步”,而是“可控”:队列满时,生产者该阻塞或丢弃,不能无脑压入。
-
std::queue本身不线程安全,所有push()/pop()必须配std::mutex保护 -
std::condition_variable用来唤醒等待线程:队列空时消费者 wait,队列满时生产者 wait(如果选择阻塞模式) - 别用
std::deque替代 —— 它的迭代器失效规则更复杂,且无容量限制接口,容易误判“满” - 初始化队列最大容量建议设为 1024 或 4096,具体看单条数据大小和延迟容忍度;设太小会频繁唤醒/阻塞,太大则失去控流意义
用 std::atomic 计数器替代锁来判断是否“过载”
如果对吞吐极度敏感,且允许少量误差(比如允许短暂超出阈值 1~2 个元素),可以用原子计数器绕过锁做快速准入判断。
典型做法是双检查:先用 std::atomic<int></int> 判断当前长度是否
- 计数器只反映近似长度,
push和pop的原子操作需严格配对,漏一次就会漂移 - 不能用它替代队列本身的线程安全逻辑 —— 它只是“快路”,真正 push/pop 还得走带锁路径
- 示例片段:
if (counter.load(std::memory_order_acquire) std::lock_guard<:mutex> lk(mtx);<br> if (queue.size() queue.push(data);<br> counter.fetch_add(1, std::memory_order_release);<br> }<br>}</:mutex>
发送线程里用 std::this_thread::sleep_for() 控制发包节奏
光控队列长度不够,还得控发送频率。比如后端服务限流 100 QPS,你队列再稳,一秒狂发 500 包照样被 reset。
别在每条数据上硬 sleep —— 那样抖动大、精度差。应该攒批 + 定时器驱动:
- 用
std::chrono::steady_clock记录上次发送时间,每次取数据前计算应等待时长,用sleep_for()补齐 - 避免用
usleep()或nanosleep()—— 它们不是 C++ 标准,跨平台行为不一致 - 如果某次积压太多,选择跳过 sleep 直接发(防雪崩),但要在日志里打
"burst_send"标记,方便事后排查 - 注意:Windows 上
sleep_for(1ms)实际可能延迟 15ms,高精度场景得用QueryPerformanceCounter自行实现微秒级等待
处理 std::system_error: Resource temporarily unavailable
这个错误大概率出现在用 epoll / kqueue 做底层 I/O 多路复用时,但你又没正确处理 EAGAIN 或 EWOULDBLOCK。它和线程模型无关,却常被误认为“多线程 bug”。
- 不是线程挂了,是 socket 设置了非阻塞模式,而 send() 缓冲区满了,内核立刻返回错误而不是等
- 正确做法:捕获该异常(或检查
errno),把未发送完的数据暂存到该连接专属的待发缓冲区,下次 epoll_wait 触发可写事件时再重试 - 切忌在 catch 块里直接
sleep(1)然后重发 —— 这会拖慢整个线程,且无法响应其他 socket 事件 - 如果你用的是
boost::asio,它内部已处理此情况,但要确保没手动调socket.non_blocking(true)后又绕过 asio 的 write 接口直调系统 send
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











