【问题标题】:Is the execution of a c++ 11 atomic object also atomic ?c++ 11 原子对象的执行也是原子的吗?
【发布时间】:2016-06-10 14:56:41
【问题描述】:

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

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

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

【问题讨论】:

  • 只有在获取锁的顺序不同时才会发生死锁。解决方法:不要。

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


【解决方案1】:

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

为了安全起见,您可以在每个需要持有锁的方法中添加对 std::unique_lock&lt;std::mutex&gt; 的引用。

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

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

【讨论】:

    【解决方案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 个单独操作有任何合理的期望。在操作之间任何事情都可能发生。

    【讨论】:

    • 即使是“小算术运算”通常也不是原子的。虽然它们发生得很快,所以测试结果可能会让你得出结论,它们是。但是,这表明测试要求不够高,而不是操作是原子的。这会给其他人留下一个令人讨厌的错误,以便将来在程序承受更多压力时找到它 - 如果你不走运,找到它的人将是最终用户而不是开发人员。
    • @Peter 我并不是要暗示小的操作,即使是单个 cpu 指令,都是自动原子的,而是只有那些类型的操作是 可以完成的原子地。
    【解决方案3】:

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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2010-09-08
      • 1970-01-01
      • 1970-01-01
      • 2010-09-20
      • 1970-01-01
      • 2011-03-03
      • 2010-12-11
      • 2020-05-07
      相关资源
      最近更新 更多