C ++并发:互斥体之外的可变可见性

时间:2016-10-07 02:53:00

标签: c++ multithreading

我很难理解变量何时被强制写入内存,甚至在互斥块之外。我为下面的复杂代码道歉,因为我已经删除了处理reader是否判断某些数据是否过时的逻辑。需要注意的重要一点是99.9%的时间,读者将采用fast path并且同步必须非常快,这就是为什么我使用原子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,然后使用它进行访问。但这是不好的做法吗?这真的有必要吗?原子操作/互斥发布操作是否可以保证读者看到更改?

谢谢!

1 个答案:

答案 0 :(得分:2)

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

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