zoukankan      html  css  js  c++  java
  • 关于多线程情况下Net-SNMP v3 版本导致进程假死情况的跟踪与分析

    关于多线程情况下Net-SNMP v3 版本导致进程假死情况的跟踪与分析

     

    1、问题描述

      在使用net-snmp对交换机进行扫描的时候经常会出现进程假死的情况(就是进程并没有死掉,但是看不到它与外界进行任何的数据交互)。这时候不知道进程内部发生了什么,虽然有日志信息,但进程已经很长时间没有动静,根本不知道这段时间做了什么。用gdb att进去发现,进行snmp发送的线程已经被阻塞了。但是阻塞的情况并不是每次都发生,而是经常发生,这就导致很难捕捉问题。通过观察日志和 tcpdump 抓包,发现这种情况只在v3版本的时候出现,那就是v3版本有什么特别的地方。


    2、调试跟踪

      观察 gdb att 后的情况,发现每次都会挂在 recvmsg() 这个函数上。刚开始以为是在进行接收的时候出的问题,多看了几层栈才发现,原来在发送函数里面就卡住了,伤心啊……

      下面是gdb看到的栈调用情况:

      在snmp v3 协议中,要想能够请求数据,首先需要获取SNMP 协议引擎,也就是engineID号,然后根据这个再去请求索要的数据。目前遇到的情况就是在请求这个引擎ID的时候卡住了。看了下net-snmp的源码,在请求引擎ID的时候是没有设置超时的,也就是死等……

    在 snmp_client.c 源文件中的 snmp_sess_synch_response() 函数中的源代码:

    复制代码
    numfds = 0;
            FD_ZERO(&fdset);
            block = NETSNMP_SNMPBLOCK;
            tvp = &timeout;
            timerclear(tvp);
            snmp_sess_select_info(sessp, &numfds, &fdset, tvp, &block);
            if (block == 1)
                tvp = NULL;         /* block without timeout */
            count = select(numfds, &fdset, 0, 0, tvp);
            if (count > 0) {
                snmp_sess_read(sessp, &fdset);
            } 
    复制代码

    而且是用的 select !!!我们发送和接收用的可是多线程呀……

    然后搜到这么一篇讨论 net-snmp 对多线程支持情况的文章:《Is Net-SNMP thread safe?

    译文在这里:《Net-SNMP是线程安全的吗

    文章开篇是这么说的,我忍不住要截图呀 !!!

    问:Net-SNMP是线程安全的吗?

    答:确切的说,不是!

    我勒个去,多么干脆的回答,简直不忍直视……

    虽然在文末给出了v3不支持多线程的原因,我还是感到不开心……

    Unfortunately, the SNMPv3 support was added about the same time as the thread support and since they occurred in parallel the SNMPv3 support was never checked for multi-threading correctness. It is most likely that it is not thread-safe at this time.


    3、分析多线程下net-snmp v3出现卡死的原因

      在多线程下,因为我的程序对交换机的发送和接收是在不同线程的,导致一个很严重的问题就是线程间的同步。

      我们假设有这样一种情况:

      我们有两个线程,发送线程T1 和 接收线程 T2。T1 只负责调用 net-snmp 的发送函数接口,向交换机请求信息;T2只负责进行接收,并且是select 方式进行接收:

      步骤:

        1)T1线程 发送一个请求,然后就不管了,接收工作就交给T2线程 select吧

        2)那么,此时这个发送线程 T1 可以继续向另一个snmp v3版本的交换机发送请求,在发送的过程中需要首先请求SNMP引擎,及请求engineID

        3)T1线程发送完了,进行阻塞调用 select,直到有消息返回才会结束阻塞状态

      注意,此时就出现问题了。我们发送请求和接收请求的时候使用两个不同的线程,也就是说,那个只负责接收的线程一直在进行select。这个时候就要看是谁能够接收到这个请求engineID的UDP包了,如果是此时的这个发送线程T1,那么万事大吉,程序继续向下走。如果这个请求engineID的包被只负责接收的线程 T2 收到了呢?那 T1 线程就没得接收了,那就只好等待了,由于没有设置超时,还是以阻塞方式进行调用的,结果就是死等……

      所以现在遇到的问题是,发送线程 T1 不仅仅只是进行了发送,还进行了接收,并且在发送过程中出现的接收动作不是我们能控制的。当然我们也可以修改源代码,增加线程锁,但那需要更多的精力去研究 net-snmp 更多的代码,还不如在自己代码里加锁。


    4、解决办法

      还能有什么解决办法?只能加线程锁了。加锁的目的在于,使发送线程 T1 的发送过程和接收线程 T2 的select过程互斥的进行,不允许接收线程 T2 抢夺发送线程 T1 的数据。

      解决方法:

        1)在 T1 线程进行发送之前先加锁,发送完成后解锁

        2)在 T2 线程 select 之前加锁,seelct结束之后解锁

      结果:运行了很长事件了,没有再出现v3卡死的情况

      但目前这种处理还是比较粗糙的,会导致T2线程在没有接收到返回数据时进行超时等待处理,一次超时等待需要3s,这对性能的损耗还是比较大的。


    5、总结

      开发的时候总会遇到各种各样的奇闻异事,习惯了就好了。今天把问题的发现及解决过程记录一下,按照茨威格在《昨日的世界》中所说的“倘若你想完全领悟伟大的杰作,你不仅要看到过它们的成品,而且必须了解到它们形成的过程”。所以我要记录下每一个问题的过程,不是因为这是什么杰作,而是因为这是我生命中的一件故事,当我垂垂暮年,回首往事,看到自己一路上记录的点滴,想起自己年轻时努力的身影,也许,这才是我送给未来自己最好的礼物吧。

    作者:郝峰波

    mail : fengbohello@qq.com

  • 相关阅读:
    redis 五大数据类型
    redis 对 key 的操作
    redis 零散知识
    redis helloworld
    CentOS下内存使用率查看
    不需要客户端插件PHP也能实现单点登录
    mysql 删匿名帐户
    mysql5.6默认情况下内存占用太大
    PHPExcel生成或读取excel文件
    通过SMTP发送邮件的Python代码
  • 原文地址:https://www.cnblogs.com/gaoshaonian/p/10791314.html
Copyright © 2011-2022 走看看