3

Lets suppose following:

I have two processes on Linux / Mac OS.

I have mmap on shared memory (or in a file).

Then in both processes I have following:

struct Data{
   volatile int reload = 0; // using int because is more standard
   // more things in the future...
};
void *mmap_memory = mmap(...);
Data *data = static_cast<Data *>(mmap_memory); // suppose size is sufficient and all OK

Then in one of the processes I do:

//...
data->reload = 1;
//...

And in the other I do:

while(...){
    do_some_work();
    //...
    if (data->reload == 1)
        do_reload();
}

Will this be thread / inter process safe?

Idea is from here:
https://embeddedartistry.com/blog/2019/03/11/improve-volatile-usage-with-volatile_load-and-volatile_store/

Note:
This can not be safe with std::atomic<>, since it does not "promise" anything about shared memory. Also constructing/destructing from two different processes is not clear at all.

Ted Lyngmo
  • 93,841
  • 5
  • 60
  • 108
Nick
  • 9,962
  • 4
  • 42
  • 80
  • Do you unset data->reload in the second process? – RoQuOTriX Aug 24 '22 at 11:36
  • @RoQuOTriX Even that's not sufficient if multiple threads/processes are running that `while(...){...}` loop or equivalent. – Andrew Henle Aug 24 '22 at 11:38
  • 1
    Dupe? [**Why is volatile not considered useful in multithreaded C or C++ programming?**](https://stackoverflow.com/questions/2484980/why-is-volatile-not-considered-useful-in-multithreaded-c-or-c-programming) I'll let others decide – Andrew Henle Aug 24 '22 at 11:39
  • 1
    I would suggest using [`std::atomic_ref`](https://en.cppreference.com/w/cpp/atomic/atomic_ref) if C++20 is available to you. – Richard Critten Aug 24 '22 at 11:40
  • @RichardCritten: You only need `atomic_ref` if you also want to more efficient non-atomic access to the same object at times. Given this question, it looks like they should just make `reload` and `atomic` member of `Data`. Possibly init with placement-new to be slightly more efficient than `data->reload.store(0, relaxed)`, or yeah that could be a reason to use `atomic_ref`, so you can assign to it cheaply while you know there's only one thread. – Peter Cordes Aug 24 '22 at 13:01
  • @PeterCordes I was worried about implicit creation of `atomic` in the shared memory and whether it was safe if the virtual addresses (from the 2 processes) were different. Does it need placement new from both processes for correct object life-times ? And if it does need placement new from 2 processes onto the same object is this ok. `std::atomic_ref` seems to make all the above questions moot. – Richard Critten Aug 24 '22 at 13:02
  • @RichardCritten: I just commented on the answer: real implementations are address-free for lock-free atomics. Non-lock-free won't work for std::atomic or std::atomic_ref. Initialization is a non-issue in practice (on POSIX systems where you'd be using `mmap`); I forget what the standard has to say about it. – Peter Cordes Aug 24 '22 at 13:06
  • 2
    If you're looking for standards, then `volatile` and `atomic` are both bad as you're not going to find ISO/IEEE-level promises about the behavior of either one relative to shared memory. That's just a gap in the standards as they exist. But if you're looking for reality, then `atomic` works fine and does what you want, while `volatile` does not. Any reasonable system will support that. Some may document it explicitly, others via their source code, others by "yes, everyone knows that is supposed to work". – Nate Eldredge Aug 24 '22 at 13:50

1 Answers1

5

Will this be thread / inter process safe?

No.

From your own link:

One problematic and common assumption is that volatile is equivalent to “atomic”. This is not the case. All the volatile keyword denotes is that the variable may be modified externally, and thus reads/writes cannot be optimized.

Your code needs atomic access to the value. if (data->reload == 1) won't work if it reads some partial/intermediate value from data->reload.

And nevermind what happens if multiple threads do read 1 from data->reload - your posted code doesn't handle that at all.

Also see Why is volatile not considered useful in multithreaded C or C++ programming?

Andrew Henle
  • 32,625
  • 3
  • 24
  • 56
  • I know is not. But some sources suggest boost uses similar technic if running on mmap. – Nick Aug 24 '22 at 12:15
  • 1
    @Nick: `std::atomic<>` works on shared memory as long as the atomic type `is_lock_free`, because real implementations follow the standard's (non-normative) recommendation that it should be address-free. [Are lock-free atomics address-free in practice?](https://stackoverflow.com/q/51463312) – Peter Cordes Aug 24 '22 at 13:05
  • 1
    @Andrew: If the only values it ever has are `0` or `1`, tearing is a non-issue in practice. The upper 3 bytes will always stay zero, and single bytes are inherently atomic on real CPUs. But yeah, there's no good reason *not* to use `std::atomic` with `memory_order_relaxed` for this, to get [well-defined C++ semantics that will compile to the same asm you'd have gotten from `volatile` on real systems](https://stackoverflow.com/questions/4557979/when-to-use-volatile-with-multi-threading/58535118#58535118). – Peter Cordes Aug 24 '22 at 13:09
  • In practice, at least GCC/clang do try to do `volatile` accesses with one instruction, making them atomic if they're sufficiently aligned and guaranteed by hardware. But again, no good reason to rely on that. – Peter Cordes Aug 24 '22 at 13:11
  • Related: [Two Different Processes With 2 std::atomic Variables on Same Address?](https://stackoverflow.com/a/59397378) shows how to `static_assert` that `std::atomic` is lock-free and has the same size as `T`. – Peter Cordes Aug 24 '22 at 13:12
  • @PeterCordes can you show me some working code? The thing I do not understand is - how I create atomic via placement new, in same memory (mmap). also how to destroy it on same mmap-ed memory? – Nick Aug 24 '22 at 13:23
  • @Nick: [Two Different Processes With 2 std::atomic Variables on Same Address?](https://stackoverflow.com/q/51229208) shows how to do that. – Peter Cordes Aug 24 '22 at 13:39
  • @Nick: There is a lot of old code out there, and similarly old advice, that predates C11/C++11 atomics, and relies on `volatile` *together with* a lot of sketchy implementation-specific behavior. See https://stackoverflow.com/questions/72841377/atomic-equivalent-for-c89/72842152#72842152 for a little more discussion. But such code/advice is not good guidance in the modern era. You have to consider your sources. – Nate Eldredge Aug 24 '22 at 13:55
  • @Nick: In real implementations of `std::atomic`, the constructor is pretty much trivial, just initializing the `T` member. And the destructor is trivial. Even non-lock-free atomics don't keep a mutex inside the `atomic` itself (usually a global hash table of spinlocks, hashing on the address) – Peter Cordes Aug 24 '22 at 13:55