经验分享:保护范围过大,给互锁开了窗口

接到一个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/

实体书看看也蛮爽

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值