Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Best way to atomically update two members of a struct?

Tags:

c++

c++11

atomic

I have a struct which contains two data members:

struct A{
    short b;
    short c;
};

and a vector containing many of these struct objects:

std::vector<A> vec;

at runtime the data members A.b and A.c from struct objects are set to zero/a value x. However, b and c must be modified at the same time- one cannot be updated separately without the other. I was planning on using an atomic compare_exchange_weak() to do the update.

I am not sure whether I should represent each struct as an std::atomic<A> in the vector, or whether I should place a union inside the struct, to combine the two shorts in to a single uint32_t and then modify this:

union A {
    struct {
         short b;
         short c;
    };

    uint32_t d;
};

What would be the best solution? Should I store a vector of:

std::vector<uint32_t>

and upon accessing each element, reinterpret_cast to A, to obtain d?

I would like the locking to be as least-intrusive as possible.

Portability is not required, this will be on Linux, 64-bit, x86, GCC 4.8+ compiler

like image 522
user997112 Avatar asked Dec 02 '14 11:12

user997112


3 Answers

Unless the hardware you are targetting supports double compare-and-swap (which is probably not the case), I think you only have two portable solutions:

  1. Introduce a higher-level lock (mutex or spinlock depending on your preference) and carry all operations on b and c within the scope of the acquired lock. A mutex is heavy, but std::atomic_flag is lock-free and very light-weight even in high-contention situations.

  2. Merge both members into a single std::atomic<int> and split that int into shorts through bit masking. Note that this requires sizeof(int) >= 2 * sizeof(short). Use fixed-size integer types if you need to enforce that.

To determine which solution is the fastest, benchmarks, of course.

If you know the number of struct A you will need at compile time, I'd suggest putting them into an std::array. If you don't, std::vector is fine as long as this number stays constant throughout the lifetime of the vector. Otherwise, since std::atomic<T> is neither copyable nor movable, you will have to write your own copy/move constructor for struct A.

like image 182
user703016 Avatar answered Nov 09 '22 09:11

user703016


I recommend wrapping the variables in a class with a getter and setter guarded by a mutex, and make the variables private.

Using an union could cause unforeseen functionality based on machine architecture and compiler flags.

EDIT Results of running a simple program that stores values of the given struct type (Linux 32bit, x86):

  • Simple store (no protection at all) -> ~4000 us
  • Mutex guarded store -> ~12000 us
  • Using an union with an atomic aggregation field -> ~21000 us
like image 41
Bogdan V. Avatar answered Nov 09 '22 08:11

Bogdan V.


Simply make a union of a large enough atomic type. This is what I use (the code snippet is not perfectly portable, using <cstdint> types instead of short and int would surely be preferrable -- but it's good enough for me as it is), and it works perfectly fine and reliably since... practically forever:

union A {
    struct {
         short b;
         short c;
    };

    std::atomic<int> d;
};

(In fact, my implementation is slightly more complicated: I'm wrapping the whole thing into another struct out of habit, so A is a struct containing a union rather than being a union. Traditionally union had weird constraints about constructors, my initial implementation predates C++0x, and my A needs a constructor. But of course using C++11's <atomic> these considerations become alltogether obsolete, since those artificial constraints no longer exist)

Note that std::atomic may be lock-free but is not guaranteed to be (except for bool). In practice, for anything the size of int or short, it is lock-free on every "serious, no-joke" architecture, and on most modern architectures it's lock-free for something of pointer size, too (though there exist exceptions, notably the very first generation of x86_64 chips from AMD).

like image 22
Damon Avatar answered Nov 09 '22 09:11

Damon