c ++ 11原子对象的执行是否也是原子的?

时间:2016-02-27 09:12:54

标签: c++ multithreading c++11 thread-safety atomic

我有一个对象,其所有功能都应按顺序执行。 我知道可以使用像

这样的互斥体来做到这一点
#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
}

但是使用这种技术在exampleMethod中调用其他互斥锁定方法后会发生死锁。

所以我正在寻找更好的解决方案。 默认的std :: atomic访问是顺序一致的,所以它不可能同时读取对这个对象的写入,但是现在当我访问我的对象并调用一个方法时,整个函数调用也是原子的或更像是

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

如果是,是否有比使用互斥锁锁定大多数函数更好的方法?

3 个答案:

答案 0 :(得分:3)

停止并考虑何时需要锁定互斥锁。如果你有许多其他函数调用的辅助函数,它可能不应该尝试锁定互斥锁,因为调用者已经有了。

如果在某些上下文中它没有被另一个成员函数调用,那么需要锁定,提供实际执行此操作的包装函数。拥有2个版本的成员函数(公共foo()和私有fooNoLock())并不罕见,其中:

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

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

根据我的经验,递归互斥体是一个code smell,表明作者没有真正了解函数的使用方式 - 不是总是错误,但当我看到一个我怀疑。

对于原子操作,它们实际上只能用于小算术运算,比如递增整数或交换2个指针。这些操作不是自动原子操作,但是当您使用原子操作时,这些操作可以用于它们。你当然不能对单个原子对象的2个单独操作有任何合理的期望。在操作之间可能发生任何事情。

答案 1 :(得分:0)

您可以使用std::recursive_mutex代替。这将允许已拥有互斥锁的线程重新获取它而不会阻塞。但是,如果另一个线程试图获取锁定,它将阻止。

答案 2 :(得分:0)

正如@BoBTFish正确指出的那样,最好将你的类的公共接口分开,哪些成员函数获得非递归锁定,然后调用不需要的私有方法。然后,您的代码必须假定在运行私有方法时始终保持锁定。

为了安全起见,您可以为需要锁定的每个方法添加对std::unique_lock<std::mutex>的引用。

因此,即使您碰巧从另一个私有方法调用一个私有方法,您也需要确保在执行之前锁定互斥锁:

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);
  }
};