简体   繁体   中英

Ordering of read/write operations in a C++ queue

Let's assume we have a SyncQueue class with the following implementation:

class SyncQueue {
    std::mutex mtx;
    std::queue<std::shared_ptr<ComplexType> > m_q;
public:
    void push(const std::shared_ptr<ComplexType> & ptr) {
        std::lock_guard<std::mutex> lck(mtx);
        m_q.push(ptr);
    }
    std::shared_ptr<ComplexType> pop() {
        std::lock_guard<std::mutex> lck(mtx);
        std::shared_ptr<ComplexType> rv(m_q.front());
        m_q.pop();
        return rv;
    }
};

then we have this code that uses it:

SyncQueue q;

// Thread 1, Producer:
std::shared_ptr<ComplexType> ct(new ComplexType);
ct->foo = 3;
q.push(ct);

// Thread 2, Consumer:
std::shared_ptr<ComplexType> ct(q.pop());
std::cout << ct->foo << std::endl;

Am I guaranteed to get 3 when ct->foo is printed? mtx provides happens-before semantics for the pointer itself, but I'm not sure that says anything for the memory of ComplexType . If it is guaranteed, does it mean that every mutex lock ( std::lock_guard<std::mutex> lck(mtx); ) forces full cache-invalidation for any modified memory locations up-till the place where memory hierarchies of independent cores merge?

std::mutex() is conformant to Mutex requirements ( http://en.cppreference.com/w/cpp/concept/Mutex )

Prior m.unlock() operations on the same mutex synchronize-with this lock operation (equivalent to release-acquire std::memory_order)

release-acquire is explained here ( http://en.cppreference.com/w/cpp/atomic/memory_order )

Release-Acquire ordering

If an atomic store in thread A is tagged memory_order_release and an atomic load in thread B from the same variable is tagged memory_order_acquire, all memory writes (non-atomic and relaxed atomic) that happened-before the atomic store from the point of view of thread A, become visible side-effects in thread B , that is, once the atomic load is completed, thread B is guaranteed to see everything thread A wrote to memory.

The synchronization is established only between the threads releasing and acquiring the same atomic variable. Other threads can see different order of memory accesses than either or both of the synchronized threads.

Code example in this section is very similar on yours. So it should be guaranteed that all writes in thread 1 will happen before mutex unlock in push().

Of course if "ct->foo = 3" hasn't any special tricky meaning where actual assignment happens in another thread :)

wrt cache-invalidation, from cppreference:

On strongly-ordered systems (x86, SPARC TSO, IBM mainframe), release-acquire ordering is automatic for the majority of operations. No additional CPU instructions are issued for this synchronization mode, only certain compiler optimizations are affected (eg the compiler is prohibited from moving non-atomic stores past the atomic store-release or perform non-atomic loads earlier than the atomic load-acquire). On weakly-ordered systems (ARM, Itanium, PowerPC), special CPU load or memory fence instructions have to be used.

So it really depends from the architecture.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

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