AI代码分析 - LocklessQueue

3 阅读4分钟

services\surfaceflinger\LocklessQueue.h

// Single consumer multi producer queue. We can understand the two operations independently to see
// why they are without race condition.
//
// push is responsible for maintaining a linked list stored in mPush, and called from multiple
// threads without lock. We can see that if two threads never observe the same value from
// mPush.load, it just functions as a normal linked list. In the case where two threads observe the
// same value, one of them has to execute the compare_exchange first. The one that doesn't execute
// the compare exchange first, will receive false from compare_exchange. previousHead is updated (by
// compare_exchange) to the most recent value of mPush, and we try again. It's relatively clear to
// see that the process can repeat with an arbitrary number of threads.
//
// Pop is much simpler. If mPop is empty (as it begins) it atomically exchanges
// the entire push list with null. This is safe, since the only other reader (push)
// of mPush will retry if it changes in between it's read and atomic compare. We
// then store the list and pop one element.
//
// If we already had something in the pop list we just pop directly.
template <typename T>
class LocklessQueue {
public:
    bool isEmpty() { return (mPush.load() == nullptr) && (mPop.load() == nullptr); }

    void push(T value) {
        Entry* entry = new Entry(std::move(value));
        Entry* previousHead = mPush.load(/*std::memory_order_relaxed*/);
        do {
            entry->mNext = previousHead;
        } while (!mPush.compare_exchange_weak(previousHead, entry)); /*std::memory_order_release*/
    }

    std::optional<T> pop() {
        Entry* popped = mPop.load(/*std::memory_order_acquire*/);
        if (popped) {
            // Single consumer so this is fine
            mPop.store(popped->mNext /* , std::memory_order_release */);
            auto value = std::move(popped->mValue);
            delete popped;
            return value;
        } else {
            Entry* grabbedList = mPush.exchange(nullptr /* , std::memory_order_acquire */);
            if (!grabbedList) return std::nullopt;
            // Reverse the list
            while (grabbedList->mNext) {
                Entry* next = grabbedList->mNext;
                grabbedList->mNext = popped;
                popped = grabbedList;
                grabbedList = next;
            }
            mPop.store(popped /* , std::memory_order_release */);
            auto value = std::move(grabbedList->mValue);
            delete grabbedList;
            return value;
        }
    }

private:
    class Entry {
    public:
        T mValue;
        std::atomic<Entry*> mNext;
        Entry(T value) : mValue(value) {}
    };
    std::atomic<Entry*> mPush = nullptr;
    std::atomic<Entry*> mPop = nullptr;
};

LocklessQueue 类解析

源文件:services/surfaceflinger/LocklessQueue.h

概述

这是一个无锁队列,采用经典的 MPSC(Multi-Producer Single-Consumer) 模式,即多线程 push、单线程 pop。在 SurfaceFlinger 中主要用于从多个线程安全地收集输入事件/消息,然后在主线程统一消费处理。

核心数据结构

class Entry {
public:
    T mValue;
    std::atomic<Entry*> mNext;
    Entry(T value) : mValue(value) {}
};

std::atomic<Entry*> mPush = nullptr;  // 多线程 push 进来的链表(栈结构,LIFO)
std::atomic<Entry*> mPop  = nullptr;  // 给消费者 pop 用的链表(FIFO 顺序)

示意图:

mPush → [entry][entry] → ...    (多线程并发插入,栈式 LIFO)
mPop  → [entry][entry] → ...    (消费者 pop 使用,队列式 FIFO)

push —— 多线程安全入队

void push(T value) {
    Entry* entry = new Entry(std::move(value));
    Entry* previousHead = mPush.load();
    do {
        entry->mNext = previousHead;
    } while (!mPush.compare_exchange_weak(previousHead, entry));
}

原理:

  1. 创建新节点 entry
  2. 读取当前 mPush 头节点 previousHead
  3. entry->mNext 指向 previousHead
  4. CAS(compare_exchange_weak) 尝试将 mPush 更新为 entry
  5. 如果 CAS 失败(说明其他线程已经修改了 mPush),previousHead 会被自动更新为最新值,重试

这是标准的 Treiber Stack 模式。如果多个线程同时读到同一个 head,只有一个 CAS 会成功,失败的线程会拿到更新后的 head 值并重试,保证了无锁安全。

pop —— 单消费者出队

std::optional<T> pop() {
    Entry* popped = mPop.load();
    if (popped) {
        // 情况1:mPop 非空,直接从 mPop 链表头部取走
        mPop.store(popped->mNext);
        auto value = std::move(popped->mValue);
        delete popped;
        return value;
    } else {
        // 情况2:mPop 为空,从 mPush 链表批量转移
        Entry* grabbedList = mPush.exchange(nullptr);
        if (!grabbedList) return std::nullopt;
        // 反转链表(LIFO → FIFO)
        while (grabbedList->mNext) {
            Entry* next = grabbedList->mNext;
            grabbedList->mNext = popped;
            popped = grabbedList;
            grabbedList = next;
        }
        mPop.store(popped);
        auto value = std::move(grabbedList->mValue);
        delete grabbedList;
        return value;
    }
}

分两种情况:

情况 1:mPop 非空

直接从 mPop 链表头部取走一个节点,O(1) 操作。由于是单消费者,不需要同步。

情况 2:mPop 为空

  1. exchange 一次性把整个 mPush 链表偷过来(原子操作,同时将 mPush 置为 nullptr)
  2. 反转链表:因为 push 端是栈式 LIFO 插入的,反转后恢复 FIFO 顺序
  3. 将反转后的链表挂到 mPop 上(最后一个节点直接返回其值)
  4. 返回第一个元素

关键设计点

特性说明
无锁 pushCAS 循环保证多线程安全,无 mutex
批量转移pop 空时一次性将 push 链表整体搬到 pop 链表,减少争用
反转链表push 端是 LIFO,反转后变 FIFO,保证正确的出队顺序
内存序代码中注释掉了显式 memory_order,使用默认的 seq_cst,保证了正确性但性能略有牺牲
单消费者约束pop 只能从一个线程调用,否则 mPop 的读写会有数据竞争

潜在问题

  1. isEmpty 不是线程安全的:读取 mPushmPop 之间可能被其他线程修改,结果不一定准确,只能作为启发式判断。
  2. 内存泄漏风险:析构函数没有清理链表,如果队列销毁时还有未 pop 的元素,会泄漏。
  3. 无边界:没有容量限制,理论上可以无限 push,没有背压(back-pressure)机制。
  4. 逐个 new/delete:每次 push 都 new 一个节点,pop 时 delete,有内存分配开销。

总结

LocklessQueue 是一个轻量级的无锁 MPSC 队列,通过 CAS 实现多线程安全的 push,通过原子 exchange 实现高效的批量链表转移,适合"多生产者高频写入、单消费者批量消费"的场景,与 SurfaceFlinger 的主线程消息循环模型高度契合。