c++ 11 原子对象的执行也是原子的吗?
Is the execution of a c++ 11 atomic object also 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;
如果是,有没有比使用互斥锁锁定大多数函数更好的方法?
您可以改用 std::recursive_mutex。这将允许已经拥有 mutex 的线程重新获取它而不会阻塞。但是,如果另一个线程试图获取锁,它将阻塞。
停下来想想你什么时候真正需要锁定一个互斥体。如果你有一些在许多其他函数中调用的辅助函数,它可能不应该尝试锁定互斥锁,因为调用者已经锁定了。
如果在某些上下文中它没有被另一个成员函数调用,因此确实需要获取锁,请提供一个实际执行此操作的包装函数。有 2 个版本的成员函数并不少见,一个 public 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 个独立操作抱有任何合理的期望。在手术之间任何事情都可能发生。
正如@BoBTFish 正确指出的那样,最好将您的 class 的 public 接口分开,哪些成员函数获取非递归锁,然后调用不获取非递归锁的私有方法。当私有方法为 运行.
时,您的代码必须假设始终持有锁
为了安全起见,您可以为每个需要持有锁的方法添加对 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);
}
};
我有一个对象,它的所有功能应该按顺序执行。 我知道可以使用像
这样的互斥体来做到这一点#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;
如果是,有没有比使用互斥锁锁定大多数函数更好的方法?
您可以改用 std::recursive_mutex。这将允许已经拥有 mutex 的线程重新获取它而不会阻塞。但是,如果另一个线程试图获取锁,它将阻塞。
停下来想想你什么时候真正需要锁定一个互斥体。如果你有一些在许多其他函数中调用的辅助函数,它可能不应该尝试锁定互斥锁,因为调用者已经锁定了。
如果在某些上下文中它没有被另一个成员函数调用,因此确实需要获取锁,请提供一个实际执行此操作的包装函数。有 2 个版本的成员函数并不少见,一个 public 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 个独立操作抱有任何合理的期望。在手术之间任何事情都可能发生。
正如@BoBTFish 正确指出的那样,最好将您的 class 的 public 接口分开,哪些成员函数获取非递归锁,然后调用不获取非递归锁的私有方法。当私有方法为 运行.
时,您的代码必须假设始终持有锁为了安全起见,您可以为每个需要持有锁的方法添加对 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);
}
};