c++multiprocessingatomic

C++ share atomic_int64_t between different process?


I am using C++ multi-processing passing data from one to another using shared memory. I put an array in the shared memory. Process A will copy data into the array, and Process B will use the data in the array. However, process B need to know how many item are in the array.

Currently I am using pipe/message queue to pass the array size from A to B. But I thinks I might put an atomic variable(like atomic_uint64_t) in the shared memory, modify it in process A and load it in process B. However I have the following questions.

  1. Is Atomic variable in C++ still atomic between process? I know atomic is implemented locking the cache line, neither another thread nor process can modify the atomic variable. So I think the answer is Yes.
  2. How exactly should I shared a atomic variable between? Can any one give an example?

Solution

  • Atomics will work, for the most part. Some caveats:

    Technically, the behavior is non-portable (and lock-free does not guarantee address-free) but basic use including acquire-release should work on all mainstream platforms.

    If you already have shared memory, the use should be pretty simple. Maybe something like this:

    struct SharedBuffer
    {
        std::atomic<std::size_t> filled;
        char buf[];
    };
    
    SharedBuffer* shared = static_cast<SharedBuffer*>(
          mmap(..., sizeof(SharedBuffer) + size, ...));
    
    fill(shared->buf);
    shared->filled.store(size, std::memory_order_release);
    

    Note that you still have to solve the issue of notifying the other process. To the best of my knowledge, you cannot use std::condition variables and std::mutex. But the OS-specific types may work. For example for pthreads, you need to set pthread_mutexattr_setpshared and pthread_condattr_setpshared.

    Maximizing portability

    int64_t may be a bit risky if your CPU architecture is 32 bit and doesn't come with 64 bit atomics. You can check at runtime with atomic_is_lock_free or at compile time with is_always_lock_free.

    Similarly, size_t may be risky if you want to mix 32 bit and 64 bit binaries. Then again, when targeting mixed binaries, you have to limit yourself to less than 32 bit address space anyway.

    If you want to provide a fallback for missing lock-free atomics, atomic_flag is guaranteed to be lock-free. So you could roll your own spinlock. Personally, I wouldn't invest the time, however. You already use OS-facilities to set up shared memory. It is reasonable to make some assumptions about the runtime platform.

    Notify / Wait

    There are a two reasons why atomic::wait and notify will not work reliably.

    1. The platform may not natively support the operation (in general or for the particular type size, see std::atomic_signed_lock_free and atomic_unsigned_lock_free
    2. Linux in particular implements the operation via its futex() system call. That has a flag FUTEX_PRIVATE_FLAG which optimizes the operation for process-local inter-thread-communication but prevents use across processes

    Since the C++ standard does not specify inter-process communication, the standard library is under no obligation to make itself work in that regime and therefore may set the flag.

    Experimentation shows that libstdc++ (for GCC) does not use the flag but Clang's libc++ does. That behavior may change in the future although I would assume that GCC does not change its behavior out of fear that some applications may depend on it.

    In comparison, Windows' WaitOnAddress() does not have such a flag. Other systems may have their own issues.

    IPC Alternatives

    Boost has inter-process atomics that should work as a drop-in replacement; including notify and wait.