由于近期我们公司的一个产品(C++),在投产之后每隔一两个月就会出现CPU%占用的问题,而且不会再降下来。
通过top -H -p [pid]查看是有两个线程占用都达到了98%,其他线程则正常。
这种情况如果是出现在测试环境,可以轻而易举的解决掉。因为测试环境可以随便使用命令查看堆栈信息等。但是由于投产的环境,
是禁止使用一些特殊命令,比如需要root权限的,比如使用之后影响应用程序服务的命令等,问题就变得很难。
在生产上使用top -H -p [pid] 查看cpu占用100%的线程,得到了线程id。但是之后又无从下手了。因为知道线程id后不知道该id对应哪个线程的代码。因为应用当中有很多类型的线程,分别负责不同的业务。
在得到这个生产问题之后,我进行了分析,得到以下两点优化点:
1)在日志中打印出线程id,这样下次出现cpu100%之后,通过线程id我可以找到出问题的线程了,然后就是分析这个线程在做什么事。
方法:(C++夸平台代码,支持win和linux系统)
//add by zyao @20190710 提供进程id和线程id获取方法
#ifdef WIN32
#define myPid() GetCurrentProcessId()
#define myTid() GetCurrentThreadId()
#else
#define myPid() getpid()
#define myTid() syscall(SYS_gettid)
#endif
经过定义以上预处理宏可以在线程中调用,然后打印出其值得到线程/进程id。以方便问题分析。
2)通过以上的方法我感觉还是麻烦,后来干脆就修改线程名称了。
应用当中开了很多类型的线程,每个线程负责其对应的业务,然后我通过查阅资料,看到prctl函数是可以设置线程名称的。
我只要将每个业务的线程名称设置为一个适当的值,然后在使用top -H -p [pid]查看线程的时候就可以直接看到那个业务的线程
cpu%了。
代码如下:(C++代码只写了linux下设置线程名称的方法)
#ifdef WIN32
#else
char buf[256] = { 0 };
sprintf(buf, "%s", "Recv");
if (strlen(buf) >= 0)
{
prctl(PR_SET_NAME, buf);
//pthread_setname_np(pthread_self(), buf); //glibc 2.12之后的版本中提供的接口
}
CKSConvertManager::GetInstance()->GetKSLog().WirteLog(__FILE__, __LINE__, "设置线程名称%s", buf);
#endif
由于我们生产服务器是运行在suse 11sp3上的,所以我只写了linux方法,我们的应用程序在windows上也能运行是跨平台的,但是在win上可以直接使用vs进行debug调试分析问题,所以无需修改线程名,故没有提供win上修改线程名称的方法。
经过以上两步改造,下次cpu100%将会能直接看到是那个业务线程出问题,然后再针对性的看代码。
当然,如果生产环境能用pstack等一些命令问题将会变得很简单,但问题是suse 11sp3生产环境压根没这命令,而且生产也不允许使用。
所以只能想其他办法慢慢分析。
当遇到cpu100%问题,当前的处理方法粗暴简单,那就是重启应用。
附:其他排查cpu100%方法(pstack生产机器没这个命令)
以多线程模块smsbu为例方法一:
第一步:查看smsbu进程ID
>>ps -ef | grep smsbu
可以看第二列显示的值便是进程ID。最后一列名称以smsbu打头的便是smsbu进程信息行。取该行的第二列数字即进程ID,举例进程id为:10228
第二步:查看进程id对应进程的线程信息
>>top -H -p 10228
可以得到进程各个线程的PID信息。即结果的第一列值。
同时可以得到各个线程占用cpu多少。找占用cpu100%的线程PID
第三步:获取线程号和线程PID对应关系
>>gdb - 10228
>>info threads
第一列id即为线程id,第四列类似(LWP 10228)中的10228即为线程pid。线程id=1的是父线程。通过PID找id
然后再继续输入
>>thread 5 (即关联线程号为5的线程)
>>bt (查看当前线程号的堆栈信息)
即可查看当前线程运行的堆栈信息。
通过堆栈信息分析,该线程运行情况,为何占用cpu过高
方法二:
第一步:查看cpu过高的进程ID。
>>top
第二步:将进程堆栈信息dump到文件中
gcore pid
第三步:将生成的core.XXXX文件取回来
第四步:在测试环境调试core文件
>>gdb bin.lexe core.xxxx (gdb [源程序] [core文件])
第五步:查看堆栈信息
步骤如方法一的第三步info threads往后 或者在gdb中使用thread apply all bt查看所有线程堆栈信息