繁体   English   中英

测试我的环形缓冲区实现的“缓存未刷新到主内存”

[英]testing “cache not flushed to main memory” for my ring-buffer implementation

更新以反映最新建议,使curWriteNum易失,重新排列了pool.Commit(); std::cout

我创建了一个单读者单作家环形缓冲区(例如ArrayPool类)。 我喜欢它,但是如果一个(阅读器)线程看不到新值,因为另一个线程在另一个CPU上运行并使用另一个缓存或类似的东西,我恐怕会遇到问题。

我已经创建了测试程序。 它创建了100个线程,所以我假设它们必须或多或少地分布在所有可用处理器上。

#include <stdint.h>
#include <iostream>
#include <boost/thread.hpp>

#include <chrono>
#include <thread>

template<class T> class ArrayPool
{
public:
    ArrayPool() {
    };

    ~ArrayPool(void) {
    };

    bool IsEmpty() {
        return curReadNum == curWriteNum;
    }

    T* TryGet()
    {
        if (curReadNum == curWriteNum)
        {
            return NULL;
        }
        T* result = &storage[curReadNum & MASK];
        ++curReadNum;
        return result;
    }

    T* Obtain() {
        return &storage[curWriteNum & MASK];
    }

    void Commit()
    {
        ++curWriteNum;
        if (curWriteNum - curReadNum > length)
        {
            std::cout <<
                "ArrayPool curWriteNum - curReadNum > length! " <<
                curWriteNum << " - " << curReadNum << " > " << length << std::endl;
        }
    }

private:
    static const uint32_t length = 65536;
    static const uint32_t MASK = length - 1;
    T storage[length];
    volatile uint32_t curWriteNum;
    uint32_t curReadNum;
};

struct myStruct {
    int value;
};

ArrayPool<myStruct> pool;

void ReadThread() {
    myStruct* entry;
    while(true) {
        while ((entry = pool.TryGet()) != NULL) {
            std::cout << entry->value << std::endl;
        }
    }
}

void WriteThread(int id) {
    std::chrono::milliseconds dura(1000 * id);
    std::this_thread::sleep_for(dura);

    myStruct* storage = pool.Obtain();
    storage->value = id;
    pool.Commit();
    std::cout << "Commited value! " << id << std::endl;
}


int main( void )
{
    boost::thread readThread = boost::thread(&ReadThread);
    boost::thread writeThread;
    for (int i = 0; i < 100; i++) {
        writeThread = boost::thread(&WriteThread, i);
    }
    writeThread.join();
    return 0;
}

我试图在2 * Xeon E5服务器上运行此程序,并且一切都很好,发现了每个值:

...
Commited value! 19
19
Commited value! 20
20
Commited value! 21
21
Commited value! 22
22
Commited value! 23
23
Commited value! 24
24
Commited value! 25
25
Commited value! 26
26
    ....

另外,在Process Explorer中,我可以看到线程数如何从〜101减少到1。这是否意味着我的ArrayPool类很好,并且不可能在现代Intel处理器上遇到任何此类问题? 如果有可能重现“缓存内存”问题,那该怎么办?

首先,没有这个程序是不安全的。 您可能无法在特定的编译器和体系结构组合上重现缓存顺序问题。 特别要注意的是,这不仅与处理器缓存有关。 理论上,您的编译器可以交换分配操作。 那么,您该怎么做才能增加风险呢?

  • 在不同的(最常见的是高)优化级别上尝试不同的编译器。 例如,如果我使用x86_64-linux-gnu-g++-4.8 -O3编译,则读者会错过所有值。 编译器很可能内联TryGet并注意到循环主体不影响条件。 因此,它可以将条件的结果缓存在寄存器值中。 为避免这种情况,您需要将条件中的变量之一标记为volatile
  • 尝试使用不同的处理器体系结构(针对不同的缓存行为)。
  • Commit和实际值写入之间,正在进行系统调用。 这需要花费大量时间。 交换这些操作。

即使您想不加锁死,您也将需要最低限度的读取障碍和写入障碍。 看一下这篇LWN文章,以了解复杂性并学习一个可帮助您编写无锁算法的库。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM