接到一个PR
调用栈顶层大致如下
from /lib64/libc.so.6+0x87eef
from /opt/xxx/lib64/MCU/libCCS.so.1+0x17388f
from /lib64/libc.so.6+0x3cb20
from /lib64/libc.so.6+0x87eed
in pthread_mutex_lock+0x112 from /lib64/libc.so.6+0x8e392
in SubscriberManager::add(const std::string &key, SubscriberCb subscriberCb)
这个栈看起来没有问题,并不是通常意义上的代码错误,比如非法内存访问
void SubscriberManager::add(const std::string &key, SubscriberCb subscriberCb)
{
std::lock_guard lk{lock_};
subscribers_[key] = subscriberCb;
}
咨询了平台层同事,之所以当前栈触发coredump,是因为所在进程为核心进程,有一个检测该进程是否keep alive的机制,即类似周期性的消息握手,如果连续3次握手超时,则认为该核心进程在异常状态,比如死循环,比如pending/block住,于是主动触发signal,抓取调用栈。
分析道这里,有经验的小伙伴,基本心理有底了。
差不多可以这样描述表面原因: 信号量获取不到,一直等待,那必然是持有该信号量的线程没有走到释放流程,考虑互锁的可能性最大。
由于另外一个可能互锁的线程不是关键线程,并没有调用栈信息,只能走读代码看看线索。
代码示意如下
subscriber_manager.h
#ifndef YOUROWN_SUBSCRIBERMANAGER_
#define YOUROWN_SUBSCRIBERMANAGER_
#include <string>
#include <functional>
#include <map>
#include <tuple>
#include <optional>
#include <mutex>
#include <memory>
using SubscriberCb = std::function<void(int A, int B)>;
class SubscriberManager
{
public:
SubscriberManager() = default;
virtual ~SubscriberManager() = default;
SubscriberManager(const SubscriberManager&) = delete;
SubscriberManager(SubscriberManager&&) = delete;
SubscriberManager& operator=(const SubscriberManager&) = delete;
SubscriberManager& operator=(SubscriberManager&&) = delete;
virtual void add(const std::string &key, SubscriberCb subscriberCb);
virtual bool subscriberFound(const std::string &key);
virtual void callEventHandler(const std::string &key, int A, int B);
private:
using Subscribers = std::map<std::string, SubscriberCb>;
Subscribers subscribers_;
std::mutex lock_;
Subscribers::const_iterator findSubscriber(const std::string &key) const;
};
#endif // YOUROWN_SUBSCRIBERMANAGER_
subscriber_manager.cpp
#include "subscriber_manager.h"
void SubscriberManager::add(const std::string &key, SubscriberCb subscriberCb)
{
std::lock_guard lk{lock_};
subscribers_[key] = subscriberCb;
}
void SubscriberManager::callEventHandler(const std::string &key, int A, int B)
{
std::lock_guard lk{lock_};
auto cit = findSubscriber(key);
if (cit != subscribers_.cend())
{
(cit->second)(A, B);
}
}
bool SubscriberManager::subscriberFound(const std::string &key)
{
std::lock_guard lk{lock_};
return findSubscriber(key) != subscribers_.cend();
}
SubscriberManager::Subscribers::const_iterator SubscriberManager::findSubscriber(const std::string &key) const
{
return subscribers_.find(key);
}
疑点就在于函数
void SubscriberManager::callEventHandler(const std::string &module,
int A, int B)
{
std::lock_guard lk{lock_};
auto cit = findSubscriber(module);
if (cit != subscribers_.cend())
{
(cit->second)(A, B);
}
}
函数callEventHandler会被上文提到的非关键线程调用,lock_保护的范围不仅仅用于保护subscribers_,也将对应的call back调用囊括进来,保护范围过大,给互锁留下了窗口。
修改方向就是缩小保护范围,只保护数据,不保护未知的操作。
方案: 新增如下函数,先拿到callback并保存,在锁保护范围之外调用该callback
std::optional<SubscriberCb> getEventHandler(const std::string &key);
void SubscriberManager::callEventHandler(const std::string &key, int A, int B)
{
auto eventHandler = getEventHandler(key);
if(eventHandler)
{
try
{
(eventHandler.value())(A, B);
}
catch (const std::exception &e)
{
//add log here
return;
}
catch (...)
{
//add log here
return;
}
}
}
std::optional<SubscriberCb> SubscriberManager::getEventHandler(const std::string &key)
{
std::lock_guard<std::mutex> lk{lock_};
auto cit = findSubscriber(key);
if (cit != subscribers_.cend())
{
return cit->second;
}
return std::nullopt;
}
这里推荐一本书籍《C++并发编程》,个人水平只看懂了前半部分,后面没有那个心力去研究
地址在 http://shouce.jb51.net/cpp_concurrency_in_action/
实体书看看也蛮爽