Is volatile required here

戏子无情 提交于 2019-12-05 17:42:44

1) must seq be volatile in this context?

Sure, most probably the read from seq will be optimized out with -O3. So yes, you should hint the compiler that seq might be changed elsewhere (i.e. in other thread) with volatile keyword.

For x86 architecture it would be enough, because x86 memory model is (almost) sequential as described on Wikipedia.

For portability, you better use atomic primitives.

2) in the context of a struct with multiple members, are only the volatile qualified variable volatile, and not the other members? i.e. is only 'seq' volatile if I only mark it volatile within the struct?

No, the data should be marked as volatile as well (or you should use atomic primitives as well). Basically, the loop:

for (;;) {
    seq1 = d->seq;
    read_data(d->data);
    seq2 = d->seq;
    if (seq1 == seq2)
        return;
}

is equivalent to:

read_data(d->data);
return;

Because the only observable effect in the code is the read_data() call.

Please note, that most likely with -O3 compiler will reorder your code quite extensively. So even for x86 architecture you will need a compiler barriers between first seq read, data read and second seq read, i.e.:

for (;;)
    {
        seq_data_type<T>* d = _data.current;
        int seq1 = d->seq;
        COMPILER_BARRIER();
        if (seq1 % 2)
            continue;

        read_function(&d->data);
        COMPILER_BARRIER();
        int seq2 = d->seq;
        if (seq1 == seq2)
            return;
    }
}

The most lightweight compiler barrier is:

 #define COMPILER_BARRIER asm volatile("" ::: "memory")

For C++11 you can use atomic_signal_fence() instead.

Overall, it is safer to use std::atomic<>: it is more portable and not that tricky as juggling with volatiles and compiler barriers...

Please also have a look at Herb Sutter's presentation called "atomic<> Weapons", which explains compiler and other memory barriers as well as atomics: https://channel9.msdn.com/Shows/Going+Deep/Cpp-and-Beyond-2012-Herb-Sutter-atomic-Weapons-1-of-2

Don't use volatile, use std::atomic<>. volatile is designed and meant to be used for interacting with memory mapped hardware, std::atomic<> is designed and meant to be used for thread synchronization. Use the right tool for the job.

Features of good std::atomic<> implementations:

  • They are lockless for standard integer types (everything up to long long, usually).

  • They work with any data type, but will use a transparent mutex for complex data types.

  • If std::atomic<> is lockless, it inserts the correct memory barriers/fences to achieve correct semantics.

  • Manipulations of std::atomic<> cannot be optimized away, they are designed for inter-thread communication after all.

As said Is volatile required here - You sholud not use volatile for inter-thread synchronization. Here is why (from C++ standard):

[..] volatile is a hint to the implementation to avoid aggressive optimization involving the object because the value of the object might be changed by means undetectable by an implementation.[...]

What volatile doesn't do is ensure that the sequence of the operations (and especially memory reads and writes) in one thread is visible in the same order in other threads (due to superscalar architecture of modern CPUs) . For this you need memory barriers or memory fences (different names for same thing). Here is some more reading that you may find useful:

If code is to be portable, volatile is never appropriate unless dealing with memory-mapped hardware. I repeat, never appropriate. Microsoft Visual C++, (x86 or x86/64), using the default compiler flags, adds some memory-order guarantees that are not in the standard. So using that compiler, with the non-standard behavior turned on, volatile might work for some multi-threading operations.

Use the standard multi-threading support, such as std::atomic, std::mutex, std::condition_variable, etc.

The actual problem is that reading from some memory (data in this case) while it's being written to is described as a data race, and therefore the behavior of the program is undefined. Even if you make seq atomic, reading from data will still cause a data race. One possible correct approach is to lock on read as well.

Answering your question of whether volatile solves the read from seq being optimized out: the compiler won't remove both reads from seq, but that doesn't solve anything, because seq is still prone to data races, resulting in undefined behavior as well. That's not what volatile is meant for, so don't abuse it.

The answer it: it depends. Do you have a reason to suspect that your compiler is not aware that code executed from within callback functions can be executed at any time? This is usually not the case on hosted system compilers (Windows/Linux etc), but may very well be the case in embedded systems, particularly bare metal or RTOS.

This topic is kind of a beaten dead horse, for example here:

What volatile does:

  • Guarantees an up-to-date value in the variable, if the variable is modified from an external source (a hardware register, an interrupt, a different thread, a callback function etc).
  • Blocks all optimizations of read/write access to the variable.
  • Prevent dangerous optimization bugs that can happen to variables shared between several threads/interrupts/callback functions, when the compiler does not realize that the thread/interrupt/callback is called by the program. (This is particularly common among various questionable embedded system compilers, and when you get this bug it is very hard to track down.)

What volatile does not:

  • It does not guarantee atomic access or any form of thread-safety.
  • It cannot be used instead of a mutex/semaphore/guard/critical section. It cannot be used for thread synchronization.

What volatile may or may not do:

  • It may or may not be implemented by the compiler to provide a memory barrier, to protect against instruction cache/instruction pipe/instruction re-ordering issues in a multi-core environment. You should never assume that volatile does this for you, unless the compiler documentation explicitly states that it does.
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!