这种锁定然后再次锁定的重构什么时候是一个坏主意
When is this lock-then-lock-again refactor a bad idea?
我有两个同级类,A
和B
,我想重构它们,以便A
是B
的父级,这样B
就可以共享A
的代码。但这种重构意味着,对于一个关键函数,锁定互斥锁两次而不是一次。有什么理由不这样做?
A类
class A{
std::map<std::string, int> values;
std::mutex mutex;
public:
//init and access functions elided
void foo(std::string key, int v){
auto i = values.find(key);
if(i == values.end())return; //Actually report error
{
std::lock_guard<std::mutex> lock(mutex);
i->second = v;
}
}
};
B类
class B{
std::map<std::string, int> values;
std::map<std::string, std::vector<int> > history;
std::mutex mutex;
public:
//init and access functions elided
void foo(std::string key, int v){
auto i = values.find(key);
if(i == values.end())return; //Actually report error
auto i2 = history.find(key);
if(i2 == history.end())return; //Actually report error
{
std::lock_guard<std::mutex> lock(mutex);
i->second = v;
i2->second.push_back(v);
}
}
};
我想写的B类的替代方案是:
class C:public A{
std::map<std::string, std::vector<int> > history;
public:
//init and access functions elided
void foo(std::string key, int v){
A::foo(key,v);
auto i2 = history.find(key);
if(i2 == history.end())return; //Actually report error
{
std::lock_guard<std::mutex> lock(mutex);
i2->second.push_back(v);
}
}
};
访问函数还使用互斥锁来锁定其读取。出于此问题的目的,假设foo()
被调用的次数比这些类中的任何其他函数都要多得多。我们还可以假设对foo()
的所有调用都是序列化的;互斥锁适用于使用 Access 函数的其他线程。
Q. 将一个互斥锁拆分为两个串行锁不会增加任何新的死锁潜力?
问。与类 A 和类 B 的代码重复,还是在基类调用中"隐藏"额外的互斥锁,是否有更大的代码气味?
问。与我在foo()
中执行的其他操作相比,锁定两次的额外开销是否微不足道?即我猜插入到地图和矢量中所需的时间至少是锁定互斥锁的 10 倍。
问。 class C
现在允许读取与读取history
不同步的values
(即,如果另一个线程在C::foo()
中间抓住了锁(。如果事实证明这是一个问题,那么回到"复制A类和B类中的代码"是唯一的设计选择吗?
这个替代方案怎么样,它添加一个返回锁的foo_impl
函数,因此可以在C::foo
中重复使用:
class A
{
std::map<std::string, int> values;
std::mutex mutex;
public:
//init and access functions elided
void foo(std::string key, int v)
{
foo_impl(key, v);
}
protected:
std::unique_lock<std::mutex> foo_impl(std::string key, int v)
{
auto i = values.find(key);
if (i == values.end()) return {}; //Actually report error
std::unique_lock<std::mutex> lock(mutex);
i->second = v;
return lock;
}
};
class C : public A
{
std::map<std::string, std::vector<int> > history;
public:
//init and access functions elided
void foo(std::string key, int v)
{
auto i2 = history.find(key);
if (i2 == history.end()) return; //Actually report error
if (auto lock = A::foo_impl(key,v))
i2->second.push_back(v);
}
};
这可确保对A::values
和C::history
的更新在单个锁下完成,因此A::values
无法在原始C::foo
中的两个锁之间再次更新。
我不
明白为什么你认为有必要锁定两次,这似乎是你想做的?
class A{
std::map<std::string, int> values;
std::mutex mutex;
protected:
void foo_unlocked(std::string key, int v){
auto i = values.find(key);
if(i != values.end())
i->second = v;
}
};
class C:public A{
std::map<std::string, std::vector<int> > history;
public:
//init and access functions elided
void foo(std::string key, int v){
auto i2 = history.find(key);
if(i2 == history.end())
return; //Actually report error
std::lock_guard<std::mutex> lock(mutex);
i2->second.push_back(v);
foo_unlocked(key, v); // do the operation in A but unlocked...
}
};
相关文章:
- 当只有一个线程主要使用该对象而其他线程很少使用它时,如何最小化该对象的互斥锁锁定?
- C++将互斥锁锁定为来自另一个线程
- 为什么在这个分解的 std::string dtor 中有一个锁定的 xadd 指令?
- 我可以在C 中的Windows中读取一个文件,而无需锁定包含文件的文件夹
- 如何创建一个新类来继承 ostream 并将其用作 cout 但带有锁定
- 某人如何在一个线程中锁定多个对象
- 使用 boost::interprocess::file_lock 创建一个锁定的文件
- 我应该如何在一个功能中锁定wxMutex,并在另一个功能中将其解锁
- mutex::lock() 检查一次解锁状态是否已经被另一个线程锁定?
- 如何确保我的进程永远不会将另一个进程锁定在文件之外
- 等待另一个进程锁定然后解锁 Win32 互斥锁
- 将读锁定升级为写锁定,而不在 C++11 中释放第一个
- 在linux上的C/C++中,我如何创建一个预锁定的互斥对象
- 当一个线程锁定一个大映射时,如何避免冻结其他线程
- c++i/o锁定一个空文件
- 这种锁定然后再次锁定的重构什么时候是一个坏主意
- 是否有必要锁定一个仅从一个线程写入且仅从另一个线程读取的数组?
- 锁定一个被取消引用的互斥锁是不是不好的行为
- 这是一个好方法来锁定一个每秒60次循环的循环吗?
- 怎么可能锁定一个GMutex两次?