Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

C++11 Can only primitive data types be declared atomic?

I was wondering, can only primitive data types be declared std::atomic in C++11? Is it possible, say, to declare a library class object to be "atomically" mutated or accessed?

For example, I might have

using namespace std::chrono;
time_point<high_resolution_clock> foo;

// setter method
void set_foo() {
  foo = high_resolution_clock::now();
}

// getter method
time_point<high_resolution_clock> get_foo() {
  return foo;
}

But, if these setter and getter methods are called in different threads, I think that may cause undefined behavior. It would be nice if I could declare foo something like:

std::atomic<time_point<high_resolution_clock>> foo;

...so that all operations on foo would be conducted in an atomic fashion. In the application for my project there are possibly hundreds of such foo variables declared across dozens of classes, and I feel it would be far more convenient to make the object mutating and accessing "atomic" so to speak, instead of having to declare and lock_guard mutexes all over the place.

Is this not possible, or is there a better approach, or do I really have to use a mutex and lock_guard everywhere?

Update:

  • Any takers? I've been fishing around the web for decent information, but there are so few examples using atomic that I can't be sure the extent to which it can be applied.
like image 810
user1930581 Avatar asked May 23 '13 13:05

user1930581


People also ask

What are the primitive data types in C?

Primitive data types in c Primitive or most fundamental data type in c can be categorized in three groups on the basis of its application: 1. Integral type number: char , int 2. Real type number: float , double 3. Void or nothing type: void

How to declare an atomic integer in C11?

We recommend C11 defines a new _Atomic () type specifier. You can declare an atomic integer like this: The C++ version has the advantage that it can be implemented without any compiler modification. It's possible to implement versions of these templates using inline assembly for each of the operations.

How do you implement atomic updates in C++?

C++ is a bit more explicit. You can use any type in the std::atomic<> template. If you had a class HugeThing containing 1MB of data, you could expect std::atomic<HugeThing> to work for atomic updates. The implementation may protect things that large with a mutex, or in any other way it chooses.

What's the difference between C++11 and C11?

C11 defines a new _Atomic () type specifier. You can declare an atomic integer like this: C++11 moves this declaration into the standard library: The C++ version has the advantage that it can be implemented without any compiler modification. It's possible to implement versions of these templates using inline assembly for each of the operations.


2 Answers

atomic<> is not restricted to primitive types. It is permitted to use atomic<> with a type T that is trivially copyable. From section 29.5 Atomic types of the c++11 standard (it also stated at std::atomic):

There is a generic class template atomic. The type of the template argument T shall be trivially copyable (3.9).

If the objects for which atomic access is required cannot be used with atomic<> then define new objects, containing the original object and a std::mutex. This means the lock_guard<> is used within the getter and setter only of the new thread safe object, and not littered throughout the code. A template might be able to define the thread safety machinery required:

template <typename T>
class mutable_object
{
public:
    mutable_object() : t_() {}
    explicit mutable_object(T a_t) : t_(std::move(a_t)) {}
    T get() const
    {
        std::lock_guard<std::mutex> lk(mtx_);
        return t_;
    }
    void set(T const& a_t)
    {
        std::lock_guard<std::mutex> lk(mtx_);
        t_ = a_t;
    }
private:
    T t_;
    mutable std::mutex mtx_;
};

using mutable_high_resolution_clock =
        mutable_object<std::chrono::time_point<
            std::chrono::high_resolution_clock>>;

using mutable_string = mutable_object<std::string>;

mutable_high_resolution_clock c;
c.set(std::chrono::high_resolution_clock::now());
auto c1 = c.get();

mutable_string s;
s.set(std::string("hello"));
auto s1 = s.get();
like image 159
hmjd Avatar answered Sep 29 '22 04:09

hmjd


Atomics are limited to trivially copyable classes (i.e. classes which have no custom copy constructor, and whose members are also trivially copyable).

This requirement has huge advantages for atomics:

  • No atomic operation can throw because a constructor threw
  • All atomics could be modeled with a lock (spinlock or mutex) and memcpy to copy data.
  • All atomics have a finite run time (bounded).

The latter is particularly useful, as atomics are sometimes implemented using spinlocks, and it is highly desired to avoid unbounded tasks while holding a spinlock. If any constructor was allowed, implementations would tend to need to fall back on full blown mutexes, which are slower than spinlocks for very small critical sections.

like image 24
Cort Ammon Avatar answered Sep 29 '22 03:09

Cort Ammon