双缓冲区+异常隔离实现无锁行情读取:写缓冲区独占解析并丢弃异常数据,读缓冲区只读快照;交换原子化,读端零异常假设,辅以版本号和有效期防静默错误。

行情解析系统对实时性、吞吐量和数据一致性要求极高,常面临“一写(行情推送)多读(策略计算、风控、展示)”的典型场景。直接在共享缓冲区上做解析并加锁,会导致读线程频繁阻塞、延迟抖动大,甚至因异常中断导致缓冲区处于中间状态。将异常处理机制与双缓冲区模式结合,核心目标不是“消灭异常”,而是让异常被隔离在写入侧、不污染读取侧,从而实现真正意义上的无锁读取。
缓冲区角色严格分离,写入失败不暴露脏数据
双缓冲本身不处理异常,但为异常隔离提供了结构基础:两个缓冲区必须有明确、不可重叠的职责边界。
- 写缓冲区(Write Buffer):仅由行情接收/解析线程独占使用。所有解析逻辑(如Protobuf反序列化、字段校验、时间戳修正)都在此缓冲区中执行;若中途抛出异常(如格式错误、校验失败、内存分配失败),该缓冲区内容直接丢弃,不清空也不交换。
- 读缓冲区(Read Buffer):仅被策略、风控等消费线程访问,且只读。它始终指向上次成功完成解析并交换过来的完整、一致的数据快照,不受当前写缓冲区是否出错的影响。
- 交换动作本身必须是原子的——例如用
std::atomic<:shared_ptr>></:shared_ptr>管理当前读取句柄,仅当写缓冲区解析完全成功后,才用store()替换读句柄;失败则维持原句柄不变。
解析异常统一捕获与降级,避免中断主流程
行情数据天然存在噪声和不规范情况,不能因单条异常消息终止整个解析循环。需在写入线程内构建轻量级异常处理链:
- 用
try/catch(...)包裹整块解析逻辑,捕获所有派生自std::exception的异常;对严重错误(如内存耗尽)记录致命日志并退出进程,其余一律降级处理。 - 降级策略包括:跳过当前消息、填充默认值(如 last_price = 0)、打标记位(
is_valid = false)、或写入旁路诊断队列供离线分析——但绝不让这些状态进入读缓冲区。 - 可配合计数器(如
std::atomic_int parse_error_count)统计异常频次,触发告警而非阻塞;当错误率超阈值时,自动切换到备用行情源或启用缓存兜底数据。
读取端零异常假设,强制数据快照语义
读线程不处理任何异常,也不检查数据有效性——这是隔离的关键。它只做三件事:
- 通过原子加载获取当前
shared_ptr<marketdata></marketdata>,确保拿到的是完整构造完毕的对象(构造函数内完成所有字段初始化)。 - 以只读方式访问其公开接口(如
getBidPrice(),getTimestamp()),所有字段在构造时已校验并转换完毕,运行时无异常分支。 - 不持有指针长期引用;每次读取都重新加载最新句柄,避免因旧缓冲区被回收导致悬垂指针——这要求
MarketData对象本身是线程安全的(如用const成员、无内部锁、无延迟初始化)。
版本号+有效期辅助验证,应对静默数据错误
异常处理解决的是显式崩溃,但行情中更危险的是“静默错误”:解析未抛异常,却生成了逻辑错误数据(如价格溢出、时间倒流)。此时需轻量级运行时防护:
- 每个
MarketData实例附带一个单调递增的version(std::atomic_uint64_t)和一个valid_until时间戳(如接收时刻 + 500ms)。 - 读线程在加载后立即检查:
if (now > data->valid_until) continue;或if (data->version —— 这些判断开销极低,且不引入锁或异常。 - 版本号在每次成功交换时由写线程原子递增,确保读线程能感知数据更新节奏,也便于调试时定位哪次解析引入了问题。











