Morcl174
Morcl174

Reputation: 93

Is the execution of a c++ 11 atomic object also atomic ?

I have a object and all its function should be executed in sequential order. I know it is possible to do that with a mutex like

#include <mutex> 

class myClass {
private:
    std::mutex mtx;
public:
    void exampleMethod();
};

void myClass::exampleMethod() {
    std::unique_lock<std::mutex> lck (mtx); // lock mutex until end of scope

    // do some stuff
}

but with this technique a deadlock occurs after calling an other mutex locked method within exampleMethod.

so i'm searching for a better resolution. the default std::atomic access is sequentially consistent, so its not possible to read an write to this object at the same time, but now when i access my object and call a method, is the whole function call also atomic or more something like

object* obj = atomicObj.load(); // read atomic
obj.doSomething(); // call method from non atomic object;

if yes is there a better way than locking the most functions with a mutex ?

Upvotes: 2

Views: 229

Answers (3)

Alexander Bessonov
Alexander Bessonov

Reputation: 513

As @BoBTFish properly indicated, it is better to separate your class's public interface, which member functions acquire non-recursive lock and then call private methods which don't. Your code must then assume a lock is always held when a private method is run.

To be safe on this, you may add a reference to std::unique_lock<std::mutex> to each of the method that requires the lock to be held.

Thus, even if you happen to call one private method from another, you would need to make sure a mutex is locked before execution:

class myClass
{
  std::mutex mtx;

//
  void i_exampleMethod(std::unique_lock<std::mutex> &)
  {
     // execute method
  }

public:
  void exampleMethod()
  {
    std::unique_lock<std::mutex> lock(mtx);
    i_exampleMethod(lock);
  }
};

Upvotes: 0

BoBTFish
BoBTFish

Reputation: 19757

Stop and think about when you actually need to lock a mutex. If you have some helper function that is called within many other functions, it probably shouldn't try to lock the mutex, because the caller already will have.

If in some contexts it is not called by another member function, and so does need to take a lock, provide a wrapper function that actually does that. It is not uncommon to have 2 versions of member functions, a public foo() and a private fooNoLock(), where:

public:
void foo() {
    std::lock_guard<std::mutex> l(mtx);
    fooNoLock();
}

private:
void fooNoLock() {
    // do stuff that operates on some shared resource...
}

In my experience, recursive mutexes are a code smell that indicate the author hasn't really got their head around the way the functions are used - not always wrong, but when I see one I get suspicious.

As for atomic operations, they can really only be applied for small arithmetic operations, say incrementing an integer, or swapping 2 pointers. These operations are not automatically atomic, but when you use atomic operations, these are the sorts of things they can be used for. You certainly can't have any reasonable expectations about 2 separate operations on a single atomic object. Anything could happen in between the operations.

Upvotes: 3

Sean
Sean

Reputation: 62472

You could use a std::recursive_mutex instead. This will allow a thread that already owns to mutex to reacquire it without blocking. However, if another thread tries to acquire the lock it will block.

Upvotes: 0

Related Questions