C++ 并发 : Variable visibility outside of mutexes

标签 c++ multithreading

<分区>

我无法理解何时强制将变量写入内存,即使是在互斥锁 block 之外。对于下面令人费解的代码,我深表歉意,因为我已经剥离了处理 reader 是否决定某些数据是否过时的逻辑。需要注意的重要一点是,99.9% 的时间,读者会走快速路径,同步必须非常快,这就是为什么我使用原子 int32 进行通信的原因陈旧性以及现在是否需要慢速路径。

我有以下设置,我“相当”确定这是无竞争的:

#define NUM_READERS 10

BigObject               mSharedObject;
std::atomic_int32_t     mStamp = 1;
std::mutex              mMutex;
std::condition_variable mCondition;
int32_t                 mWaitingReaders = 0;

void reader() {
    for (;;) { // thread loop
        for (;;) { // spin until stamp is acceptible
            int32_t stamp = mStamp.load();
            if (stamp > 0) { // fast path
                if (stampIsAcceptible(stamp) && 
                    mStamp.compare_exchange_weak(stamp, stamp + 1)) {
                    break;
                }
            } else { // slow path
                // tell the loader (writer) that we're halted
                std::unique_lock<mutex> lk(mMutex);
                mWaitingReaders++;
                mCondition.notify_all();
                while (mWaitingReaders != 0) {
                    mCondition.wait(lk);
                } // ###
                lk.unlock();
                // *** THIS IS WHERE loader's CHANGES TO mSharedObject
                // *** MUST BE VISIBLE TO THIS THREAD!
            }
        }
        // stamp acceptible; mSharedObject guaranteed not written to

        mSharedObject.accessAndDoFunStuff();

        mStamp.fetch_sub(1); // part of hidden staleness logic
    }
}

void loader() {
    for (;;) { // thread loop
        // spin until we somehow decide we want to change mSharedObject!
        while (meIsHappySleeping()) {}

        // we want to modify mSharedObject, so set mStamp to 0 and wait
        // for readers to see this and report that they are now waiting
        int32_t oldStamp = mStamp.exchange(0);
        unique_lock<mutex> lk(mMutex);
        while (mWaitingReaders != NUM_READERS) {
            mCondition.wait(lk);
        }
        // all readers are waiting. start writing to mSharedObject
        mSharedObject.loadFromFile("example.foo");
        mStamp.store(oldStamp);
        mWaitingReaders = 0; // report completion
        lk.unlock();
        mCondition.notify_all();
        // *** NOW loader's CHANGES TO mSharedObject
        // *** MUST BE VISIBLE TO THE READER THREADS!
    }
}

void setup() {
    for (int i = 0; i < NUM_READERS; i++) {
        std::thread t(reader); t.detach();
    }
    std::thead t(loader); t.detach();
}

*** 星号标记的部分是我关心的。这是因为虽然我的代码排除了竞争(据我所知),mSharedObject 在被 loader() 写入时仅受互斥体保护。因为 reader() 需要非常快(如上所述),我不希望它对 mSharedObject 的只读访问必须由互斥锁保护。

一个“保证”的解决方案是在 ### 行引入一个线程局部变量 const BigObject *latestObject,它被设置为 &mSharedObject 然后使用它进行访问。但这是不好的做法吗?而且真的有必要吗?原子操作/互斥锁释放操作能保证读者看到变化吗?

谢谢!

最佳答案

无锁代码,甚至仅使用原子锁定代码都远非简单。要做的第一件事就是添加一个互斥锁并分析同步过程中实际损失了多少性能。请注意,互斥锁的当前实现可能只是快速执行自旋锁,这在无竞争时大致是一个原子操作。

如果您想尝试无锁编程,您需要研究原子操作的内存排序参数。作者将需要 ..._release 与执行 ..._acquire 的读者同步(或在双方使用顺序一致性)。否则,对任何其他变量的读/写可能不可见。

关于C++ 并发 : Variable visibility outside of mutexes,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/39908634/

相关文章:

multithreading - 大规模多线程设计的共享内存缺陷

java - Java中的绿色线程和 native 线程

python - python 中的线程和动态列表问题

c++ - 为什么下面定义了一个转换运算符?

c++ - 遍历 std::set 中包含的所有三重不同值?

c++ - 初始化构造函数的默认参数的首选方法是什么?

python - Python 中 sqlite 的多次读取

c++ - 在正确的全局运算符上推导出错误的成员运算符

c++ - 为什么 C++ 编译器不转换模板函数参数以匹配预期的结果类型?

android - 使用线程循环图像