C++03基于gcc __sync实现的无锁多写单读环形队列小容量测试失败求解
实现存在的核心错误整理
1. 写槽提交逻辑导致读线程提前漏读
多写线程并发场景下,写线程成功申请到m_tailWriting对应的槽位、写入数据后,需要通过CAS更新m_tailReady才能让读线程感知到该条数据。如果某个写线程已经完成数据写入、但还没来得及更新m_tailReady时,其余所有写线程都已执行完毕被join,workingWriter被减为0,此时读线程判断Count() == 0(因为m_tailReady还未更新)且workingWriter == 0,就会直接退出。已经写入但未提交m_tailReady的元素不会被读取,最终导致pushed > popped,测试返回false。
小容量队列因为槽位更少,并发写入时更容易出现写槽申请后未及时提交的场景,所以触发概率更高。
2. Count函数取值不可靠导致队列空判断错误
Count()函数读取m_tailReady和m_headReady时没有添加内存屏障,也没有保证读取顺序的一致性,多核心场景下可能读到两个变量的不一致快照:明明队列还有已提交的可读取元素,Count()却返回0,触发读线程提前退出。
3. 测试线程join逻辑存在时序隐患
测试代码中workingWriter是在写线程被join之后才减1,而写线程执行完所有写入逻辑到被join之间存在时间差,可能出现写线程已经停止写入、但workingWriter还未更新为0的空窗期,也可能反向出现workingWriter已经为0但还有写线程未完成m_tailReady提交的情况,进一步放大漏读概率。
修复建议
- 调整读线程退出逻辑:新增独立的全局标识标记所有写线程完成写入,读线程在所有写线程完成后,再多轮询几次队列,确认所有已提交的元素都被读取后再退出。
- 修复Count函数:读取两个索引前添加读内存屏障,采用两次读取
m_headReady夹取m_tailReady的方式,保证取值的一致性。 - 优化Pop逻辑:单读线程场景下,
m_headReady只有读线程会修改,无需用原子交换指令,直接赋值即可降低开销。
内容的提问来源于stack exchange,提问作者Yl6482B5
相关产品推荐
相关产品推荐

