假设我们想使用A
使类std::mutex
成为线程安全的。我的复制构造函数和赋值运算符与下面的代码类似:
#include <mutex>
class A {
private:
int i;
mutable std::mutex mtx;
public:
A() : i(), mtx() { }
A(const A& other) : i(), mtx()
{
std::lock_guard<std::mutex> _lock(other.mtx);
i = other.i;
}
A& operator=(const A& other)
{
if (this!=&other) {
std::lock_guard<std::mutex> _mylock(mtx), _otherlock(other.mtx);
i = other.i;
}
return *this;
}
int get() const
{
std::lock_guard<std::mutex> _mylock(mtx);
return i;
}
};
我不认为它有任何问题,除了other
被复制之前被另一个线程销毁的可能性,我可以处理。
我的问题是我没有在任何地方看到这种模式,所以我不知道人们是否只是没有需要,或者由于我目前看不到的原因显然是错误的。
由于
注意:
这只是一个例子。我可以拥有任意类型的任意数量的成员变量,它不必只是int
。
在Martin York对可能的死锁的评论之后,这是一个使用复制和交换的更新版本(复制省略也是可能的,但它无法有效处理自我分配案例)。
我也将int改为T,所以人们不能认为它是POD。
template<typename T>
class A {
private:
T t;
mutable std::mutex mtx;
public:
A() : t(), mtx() { }
A(const A& other) : t(), mtx()
{
std::lock_guard<std::mutex> _lock(other.mtx);
t = other.t;
}
A& operator=(const A& other)
{
if (this!=&other) {
A tmp(other);
std::lock_guard<std::mutex> _lock(mtx);
std::swap(t, tmp.t);
}
return *this;
}
T get() const
{
std::lock_guard<std::mutex> _lock(mtx);
return t;
}
};
答案 0 :(得分:4)
老问题,新答案:
Imho,处理原始复制赋值运算符的死锁问题的更好方法是:
A& operator=(const A& other)
{
if (this!=&other) {
std::unique_lock<std::mutex> _mylock(mtx, std::defer_lock),
_otherlock(other.mtx, std::defer_lock);
std::lock(_mylock, _otherlock);
i = other.i;
}
return *this;
}
即。使用std::lock(L1, L2)
同时锁定两个互斥锁而不用担心死锁。这可能比复制/交换习惯用语具有更高的性能,特别是如果成员数据由std::vector
,std::string
或包含向量和/或字符串的类型组成。
在C ++ 1y中(我们希望y为4),有一个新的<shared_mutex>
标头提供读/写锁定功能,可能提供性能提升(性能测试将是具体用例需要确认)。以下是它的使用方法:
#include <mutex>
#include <shared_mutex>
class A {
private:
int i;
mutable std::shared_mutex mtx;
public:
A() : i(), mtx() { }
A(const A& other) : i(), mtx()
{
std::shared_lock<std::shared_mutex> _lock(other.mtx);
i = other.i;
}
A& operator=(const A& other)
{
if (this!=&other) {
std::unique_lock<std::shared_mutex> _mylock(mtx, std::defer_lock);
std::shared_lock<std::shared_mutex> _otherlock(other.mtx, std::defer_lock);
std::lock(_mylock, _otherlock);
i = other.i;
}
return *this;
}
int get() const
{
std::shared_lock<std::shared_mutex> _mylock(mtx);
return i;
}
};
即。这与原始代码非常相似(修改为使用std::lock
,就像我上面所做的那样)。但是成员互斥锁类型现在是std::shared_mutex
而不是std::mutex
。并且当保护const A
(并且假设除了互斥锁之外没有可变成员)时,只需要在“共享模式”下锁定互斥锁。使用shared_lock<shared_mutex>
可以轻松完成此操作。当您需要以“独占模式”锁定互斥锁时,您可以根据需要使用unique_lock<shared_mutex>
或lock_guard<shared_mutex>
,并使用与std::mutex
使用此功能相同的方式。
特别注意,现在许多线程可以同时从同一A
复制构造,甚至可以从复制同一A
。但是,一次只能有一个帖子可以复制分配同一个A
。
答案 1 :(得分:2)
忽略所有实现细节,您没有看到此模式的原因是因为很可能您正在锁定错误的抽象级别。
get()
函数更多(超过一个成员和更多成员),则从该对象读取可能/将导致数据不一致。获取正确的多线程代码不仅仅是确保没有“崩溃”并且单个对象保持一致状态。如果你(认为你)需要上述方案,你可能会认为当你的应用仍在做错事时你会保存。
至于实现细节:由于您已经在使用C ++ 0x,因此您还应该实现适当定义的移动操作。
答案 2 :(得分:1)
我不是这方面的权威,因为多线程很棘手,但到目前为止看起来很好 。 顺便说一下,你可能意味着
std::lock_guard<std::mutex>
并在copy-ctor中:
A(const A& other) : mtx()
{
std::lock_guard<std::mutex> _lock(other.mtx);
i = other.i;
}
确保other
的线程安全的另一种方法是仅使用“安全”的getter来访问它,尽管在调用多个getter时这不会按预期运行。但是,要小心参考!
答案 3 :(得分:0)
你没有真正看到它,因为标准的线程设施非常新,而且我不知道支持它们的单个编译器 - 你会进一步寻找boost :: thread示例。此外,您无偿使用同步可能会导致性能不佳,但这只是我的意见。
答案 4 :(得分:0)
这更正确,但并不完全健壮:
#include <mutex>
class A {
private:
int i;
std::mutex mtx;
public:
A() : i(0), mtx() {
}
/* this is one option for implementation, but would be rewritten when there are more ivars in order to reduce acquisition counts */
A(A& other) : i(other.get()), mtx() {
}
~A() {
/* unsafe if subclassed, also useful for determining whether objects are destroyed prematurely (e.g., by their containers */
std::lock_guard<std::mutex> _mylock(this->mtx);
}
A& operator=(A& other) {
std::lock_guard<std::mutex> _mylock(this->mtx);
std::lock_guard<std::mutex> _otherlock(other.mtx);
this->i = other.i; /* you could call other.get() and bypass the lock_guard, but i'm assuming there's really more work to be done here */
return *this;
}
int get() {
std::lock_guard<std::mutex> _mylock(this->mtx);
return this->i;
}
private:
/* prohibited */
A(const A& other);
/* also prohibited */
A& operator=(const A& other);
};