无锁队列入队实现问题排查:TryStartWriting方法是否存在缺陷?
已知场景:QueryWorker是一个类,QueryWorker.Next是该类的字段;TryStartWriting方法用于将Writer入队并置于队尾,若当前元素成为队首则返回true;出操作为单消费者,无需加锁。
待分析代码
internal bool TryStartWriting(QueryWorker Writer) { QueryWorker? Last; do { Last = WriterQueueHead; while (Last is null) { if (Interlocked.CompareExchange(ref WriterQueueHead, Writer, null) is null) return true; Last = WriterQueueHead; } while (Last.Next is not null) Last = Last.Next; } while (Interlocked.CompareExchange(ref Last.Next, Writer, null) is not null); WriterQueueHead = Writer; return false; }
存在的核心问题
队列头被强制覆盖,直接丢失原有队列
代码最后一行WriterQueueHead = Writer;是致命错误:无论当前队列已有多少元素,这行代码都会把队列头直接指向新入队的Writer,导致之前的整个队列链表完全丢失,队列结构被彻底破坏。遍历队尾时存在竞态条件
多线程同时入队时,while (Last.Next is not null) Last = Last.Next;这段遍历队尾的过程是非原子操作。如果在当前线程遍历到某个节点时,其他线程已经修改了该节点的Next字段,当前线程会遍历到错误的位置,后续的Interlocked.CompareExchange可能会覆盖其他线程刚插入的元素,或者将新元素插入到链表中间,造成队列元素丢失、链表断裂等异常。空队列处理逻辑衔接混乱
处理空队列的while (Last is null)循环中,虽然通过Interlocked.CompareExchange保证了队列头初始化的原子性,但后续逻辑没有正确衔接——当队列被其他线程初始化后,当前线程会继续执行遍历队尾的操作,但结合最后覆盖队列头的错误代码,整个逻辑完全不符合无锁入队的设计预期。
总结
这个入队实现存在严重的逻辑错误,不仅会直接丢失队列数据,还会在多线程场景下引发链表结构异常,完全无法正确实现无锁入队的功能。
内容的提问来源于stack exchange,提问作者Anton Christiansen

