Do I need a memory barrier for the change notification flag between threads?

I need a very fast (in the sense of "low cost to the reader" rather than "low latency") mechanism for notifying change between threads to update the read cache:

Situation

The W (Writer) theme updates the data structure ( S ) (in my case, the parameter on the map) only once at a time.

The R (Reader) theme supports S cache and reads this very often. When Thread W updates S Thread R should be notified of the update in a reasonable amount of time (10-100 ms).

The architecture is ARM, x86, and x86_64. I need to support C++03 with gcc 4.6 and higher.

code

It looks something like this:

 // variables shared between threads bool updateAvailable; SomeMutex dataMutex; std::string myData; // variables used only in Thread R std::string myDataCache; // Thread W SomeMutex.Lock(); myData = "newData"; updateAvailable = true; SomeMutex.Unlock(); // Thread R if(updateAvailable) { SomeMutex.Lock(); myDataCache = myData; updateAvailable = false; SomeMutex.Unlock(); } doSomethingWith(myDataCache); 

My question

There are no blockages or barriers in the R fast path (no update). This is mistake? What are the implications of this project?

Do I need to qualify updateAvailable as volatile ?

Will R get the update eventually?

My understanding is still

Is data sequence safe?

This is a bit like Double Checked Locking. According to http://www.cs.umd.edu/~pugh/java/memoryModel/DoubleCheckedLocking.html , you can use a memory barrier to fix it in C ++.

However, the main difference is that the share is never affected / read on the Reader fast path. When updating the cache, consistency is guaranteed by the mutex.

Will R receive an update?

That's where it gets complicated. As I understand it, a processor working with thread R can cache updateAvailable indefinitely, effectively moving the read path to the actual if .

Thus, the upgrade may take until the next cache, for example, when another thread or process is scheduled.

+5
source share
3 answers

Do I need to qualify updateAvailable as volatile ?

Since volatile does not correlate with the thread model in C ++, you should use atomatics to ensure strict confirmation of your program:

In C++11 or newer, the preferred way is to use atomic<bool> with memory_order_relaxed store / load:

 atomic<bool> updateAvailable; //Writer .... updateAvailable.store(true, std::memory_order_relaxed); //set (under mutex locked) // Reader if(updateAvailable.load(std::memory_order_relaxed)) // check { ... updateAvailable.store(false, std::memory_order_relaxed); // clear (under mutex locked) .... } 

gcc, since 4.7 supports similar functionality with atomic built-in .

As with gcc 4.6, it seems that there is no strictly confirming way to avoid fencing when the updateAvailable variable is updateAvailable . In fact, memory sampling is usually much faster than 10-100 m. Thus, you can use your own atomic built-in functions :

 int updateAvailable = 0; //Writer ... __sync_fetch_and_or(&updateAvailable, 1); // set to non-zero .... //Reader if(__sync_fetch_and_and(&updateAvailable, 1)) // check, but never change { ... __sync_fetch_and_and(&updateAvailable, 0); // clear ... } 

Is data sequence safe?

Yes, it is safe. Your reason is absolutely correct:

the shared resource is never affected / read on the fast read path.


This is NOT a double check lock!

This is explicitly stated in the question itself.

In the case where updateAvailable is false, the Reader thread uses the myDataCache variable, which is local to the thread (other threads do not use it). In a double-check locking scheme, all threads use the shared object directly.

Why no fences / memory barriers are needed here

The only variable available at the same time is updateAvailable . The myData variable is accessed using mutex protection, which provides all the necessary fences. myDataCache is local to the Reader thread.

When the read stream sees the updateAvailable variable as false, it uses the myDataCache variable, which is changed by the stream itself . Software warranties guarantee the correct visibility of changes in this case.

Regarding guaranteed visibility for the updateAvailable variable, the C ++ 11 standard provides such guarantees for the atomic variable, even without fences. 29.3 p13 says:

Implementations should make nuclear storage visible to atomic loads within a reasonable amount of time.

Jonathan Wackel has confirmed that this paragraph applies even to memory_order_relaxed to access chat .

+1
source

Use C ++ atom atoms and create updateAvailable a std::atomic<bool> . The reason for this is that not only the processor can see the old version of the variable, but especially the compiler, which does not see the side effect of another thread and thus never bothers to re-configure the variable, so you will never see the updated value in the stream. In addition, this way you get a guaranteed atomic read, which you do not have if you just read the value.

In addition, you can get rid of the lock if, for example, the manufacturer only ever creates data when updateAvailable is false, you can get rid of the mutex because std::atomic<> provides the correct reading order and writes. If this is not the case, you will still need a lock.

+2
source

Here you need to use a memory fence. Without a fence, there are no warranty updates that will ever appear on another thread. In C ++ 03, you can use either the platform-specific ASM code ( mfence for Intel, I don’t know about ARM), or use the atomic set / get functions provided by the operating system.

+2
source

Source: https://habr.com/ru/post/1236958/


All Articles