开源项目readerwriterqueue学习

开源项目readerwriterqueue学习

前置知识

内存屏障-Memory Barrier

内存屏障(Memory Barrier)是什么?

内存屏障是一种CPU指令,用于控制内存操作的顺序。它能保证在多线程或多核环境中,某些内存读写操作按照预期的顺序执行,以防止编译器或 CPU 重排序 导致的数据不一致问题。


为什么需要内存屏障?

  • 编译器优化: 编译器可能会为了提高性能,重新排序代码中的读写操作顺序。
  • CPU 重排序: 现代 CPU 为了提高指令流水线的效率,也会对指令进行重排序执行。
  • 多核缓存一致性: 在多核 CPU 中,每个核心有自己的缓存,如果不加以控制,不同核心之间对共享内存的访问可能出现不一致的情况。

例如,在下面这段代码中:

// 线程 1:
x = 1;
flag = 1;

// 线程 2:
if (flag == 1) {
    print(x);
}

可能出现的重排序:

  • 编译器可能会把 x = 1;flag = 1; 顺序对调。
  • CPU 可能先把 flag = 1; 写入内存,再把 x = 1; 写入。
  • 这会导致线程 2flag 为 1,但 x 还是初始值,出现未定义行为

内存屏障的作用:

内存屏障通过禁止重排序,确保某些指令之前的内存操作完成后,才能执行后续的指令。

  • 写内存屏障(Store Memory Barrier,StoreStore): 确保屏障之前的所有写操作在内存中完成后,才能进行屏障之后的写操作。
  • 读内存屏障(Load Memory Barrier,LoadLoad): 确保屏障之前的所有读操作完成后,才能进行屏障之后的读操作。
  • 全内存屏障(Full Memory Barrier,FullFence): 既保证读操作也保证写操作的顺序。

在 C++ 中的实现:

C++11 提供了 std::atomic_thread_fence 来显式地插入内存屏障,例如:

#include <atomic>

int x = 0;
int flag = 0;

void thread1() {
    x = 1;
    std::atomic_thread_fence(std::memory_order_release); // 写内存屏障
    flag = 1;
}

void thread2() {
    if (flag == 1) {
        std::atomic_thread_fence(std::memory_order_acquire); // 读内存屏障
        // 确保读取 flag == 1 时,x 一定已被写入 1
        std::cout << x << std::endl;
    }
}
  • std::memory_order_release 确保 x = 1flag = 1 之前完成。
  • std::memory_order_acquire 确保 flag = 1 被读取后,再读取 x 时一定是最新值。

在 x86 架构上的特点:

  • x86 CPU 上,写操作不会重排序读操作可能重排序
  • 因此,x86 上的写内存屏障通常会被优化为无操作(No-Op)
  • 读内存屏障 会被编译成 LFENCE 指令,确保读操作顺序。
  • 全内存屏障 则被编译成 MFENCE 指令,强制读写顺序。

总结:

  • 内存屏障 是一种CPU指令,用于防止重排序,保证多线程环境下的内存可见性一致性
  • C++ 中,std::atomic_thread_fence 可以显式插入内存屏障。
  • x86 架构上,写内存屏障通常是No-Op,读内存屏障为 LFENCE,全内存屏障为 MFENCE

获取队列元素个数

README里的这段示例代码当中:

BlockingReaderWriterQueue<int> q;

std::thread reader([&]() {
    int item;
#if 1
    for (int i = 0; i != 100; ++i) {
        // Fully-blocking:
        q.wait_dequeue(item);
    }
#else
    for (int i = 0; i != 100; ) {
        // Blocking with timeout
        if (q.wait_dequeue_timed(item, std::chrono::milliseconds(5)))
            ++i;
    }
#endif
});
std::thread writer([&]() {
    for (int i = 0; i != 100; ++i) {
        q.enqueue(i);
        std::this_thread::sleep_for(std::chrono::milliseconds(10));
    }
});
writer.join();
reader.join();

assert(q.size_approx() == 0);

这里的approx指的是approximate,解释如下:

approxapproximate 的缩写,意思是近似的

在这行代码中:

assert(q.size_approx() == 0);

size_approx() 的意思是 “近似大小”

  • 它返回的是队列中元素的近似数量,而不是精确值
  • 这是因为无锁队列通常在高并发场景下使用,为了保证性能,不会加锁精确计算大小,而是给出一个大致的估计值
  • 例如,在某些线程正在 enqueue()dequeue() 时,size_approx() 可能返回一个略微偏大的或偏小的值。

这有助于提高性能,但可能不完全准确

如果需要精确值,则需要额外的同步机制,但会影响性能。

posted @ 2025-02-16 16:39  Gold_stein  阅读(16)  评论(0编辑  收藏  举报