从地图上擦除时崩溃
Crash during erase from map
由于意外行为导致我崩溃了。我想问一下我如何修改代码来防御这次崩溃发生了什么。
void SubscManag::handleNotif(Notif notif)
{
std::cout << "Current subscribe size: " << subs_.size();
std::map<int, SubscData>::iterator it;
{
std::lock_guard<std::recursive_mutex> lock(mutex_);
it = subs_.begin();
}
for (;;)
{
std::lock_guard<std::recursive_mutex> lock(mutex_);
if (it == subs_.end())
{
break;
}
if (it->second.hasToBeErased)
{
std::cout << "Erase id : " << it->first;
it = subs_.erase(it);
continue;
}
if (cond)
{
// ....
++it;
continue;
}
// ....
++it;
}
}
崩溃发生在 it = subs_.erase(it) 行;
有了这个
Erase id : 2
Erase id : 3
Erase id : 28473456
所以可能 Element with id : 28473456
不存在或已损坏,但我如何防止崩溃?
谢谢。
您正在尝试进行基于锁的并发。
基于锁的并发不构成。本地正确的操作,连接时会产生垃圾。
这里,问题是你的it
是在一把锁中生成的,然后你解锁。在解锁期间,it
可能有效也可能无效。然后你重新锁定并假设 it
仍然有效。
你要么必须锁定你在地图上工作的整个时间段,然后解锁它并忘记你在锁中知道的几乎所有东西(没有携带迭代器),根据不可变数据结构重写你的代码,或者做一些更激进的事情。
{
std::lock_guard<std::recursive_mutex> lock(mutex_);
it = subs_.begin();
}
这里是锁定,执行操作,然后...解锁作用域末尾的互斥量。
所以 it
是映射的迭代器,并且在您持有映射时不会阻止其他线程编辑映射。这可能会使 it
.
无效
同样的事情发生在你的 for(;;)
循环中;您在每次迭代开始时锁定,然后在结束时解锁。 it
引用的地图元素可能会被删除,从而使您的 it
在一次迭代和下一次迭代之间失效。
这是一个简单的实用程序 class:
template<class T>
struct mutex_guarded {
template<class F>
auto read(F&& f)const {
auto l = std::unique_lock<std::mutex>(m);
return f(t);
}
template<class F>
auto write(F&& f) {
auto l = std::unique_lock<std::mutex>(m);
return f(t);
}
private:
mutable std::mutex m;
T t;
};
现在,将您的 std::map<int, SubscData>
更改为 mutex_guarded<std::map<int, SubscData>>
。
下一个:
void SubscManag::handleNotif(Notif notif)
{
subs_.write([&](auto& subs_) {
std::cout << "Current subscribe size: " << subs_.size();
auto it = subs_.begin();
for (;;)
{
if (it == subs_.end())
{
break;
}
if (it->second.hasToBeErased)
{
std::cout << "Erase id : " << it->first;
it = subs_.erase(it);
continue;
}
if (cond)
{
// ....
++it;
continue;
}
// ....
++it;
}
});
}
并且您将在访问 subs_
.
时锁定互斥锁
请注意,通过其他方法访问 subs_
仍然很危险。用互斥量保护单个方法调用是一个糟糕的计划。使“可以从任何线程访问此对象”是 999/1000 情况下的灾难,通常会延迟,最初的小情况会起作用,然后在编写代码后很久就会在死锁和竞争条件下爆炸。
mutex_guarded
不是最好的解决方案,但它比按方法锁定要好。
由于意外行为导致我崩溃了。我想问一下我如何修改代码来防御这次崩溃发生了什么。
void SubscManag::handleNotif(Notif notif)
{
std::cout << "Current subscribe size: " << subs_.size();
std::map<int, SubscData>::iterator it;
{
std::lock_guard<std::recursive_mutex> lock(mutex_);
it = subs_.begin();
}
for (;;)
{
std::lock_guard<std::recursive_mutex> lock(mutex_);
if (it == subs_.end())
{
break;
}
if (it->second.hasToBeErased)
{
std::cout << "Erase id : " << it->first;
it = subs_.erase(it);
continue;
}
if (cond)
{
// ....
++it;
continue;
}
// ....
++it;
}
}
崩溃发生在 it = subs_.erase(it) 行; 有了这个
Erase id : 2
Erase id : 3
Erase id : 28473456
所以可能 Element with id : 28473456
不存在或已损坏,但我如何防止崩溃?
谢谢。
您正在尝试进行基于锁的并发。
基于锁的并发不构成。本地正确的操作,连接时会产生垃圾。
这里,问题是你的it
是在一把锁中生成的,然后你解锁。在解锁期间,it
可能有效也可能无效。然后你重新锁定并假设 it
仍然有效。
你要么必须锁定你在地图上工作的整个时间段,然后解锁它并忘记你在锁中知道的几乎所有东西(没有携带迭代器),根据不可变数据结构重写你的代码,或者做一些更激进的事情。
{
std::lock_guard<std::recursive_mutex> lock(mutex_);
it = subs_.begin();
}
这里是锁定,执行操作,然后...解锁作用域末尾的互斥量。
所以 it
是映射的迭代器,并且在您持有映射时不会阻止其他线程编辑映射。这可能会使 it
.
同样的事情发生在你的 for(;;)
循环中;您在每次迭代开始时锁定,然后在结束时解锁。 it
引用的地图元素可能会被删除,从而使您的 it
在一次迭代和下一次迭代之间失效。
这是一个简单的实用程序 class:
template<class T>
struct mutex_guarded {
template<class F>
auto read(F&& f)const {
auto l = std::unique_lock<std::mutex>(m);
return f(t);
}
template<class F>
auto write(F&& f) {
auto l = std::unique_lock<std::mutex>(m);
return f(t);
}
private:
mutable std::mutex m;
T t;
};
现在,将您的 std::map<int, SubscData>
更改为 mutex_guarded<std::map<int, SubscData>>
。
下一个:
void SubscManag::handleNotif(Notif notif)
{
subs_.write([&](auto& subs_) {
std::cout << "Current subscribe size: " << subs_.size();
auto it = subs_.begin();
for (;;)
{
if (it == subs_.end())
{
break;
}
if (it->second.hasToBeErased)
{
std::cout << "Erase id : " << it->first;
it = subs_.erase(it);
continue;
}
if (cond)
{
// ....
++it;
continue;
}
// ....
++it;
}
});
}
并且您将在访问 subs_
.
请注意,通过其他方法访问 subs_
仍然很危险。用互斥量保护单个方法调用是一个糟糕的计划。使“可以从任何线程访问此对象”是 999/1000 情况下的灾难,通常会延迟,最初的小情况会起作用,然后在编写代码后很久就会在死锁和竞争条件下爆炸。
mutex_guarded
不是最好的解决方案,但它比按方法锁定要好。