我首先声明,我有一些标题党的嫌疑,确实没有什么血案发生,顶多是我找BUG 时由于太用力把嘴唇给咬破了。
这个故事发生在刚刚过去的猴年春节前夕,公司准备上传最后一个包到appstore,然后大家各回各家各找各妈。大家憧憬着即将到来的假期写着代码,以完成最后一个版本。由于这时候程序猿的春天即将到来,内心总是蠢蠢欲动的,代码自然而然的写的有点糙。在测试即将完成进入性能测试步骤时,发现手机在使用一段时间后会发烫,并且程序卡顿的要死。由于这种现象是在一段时间后才发生的,所以一开始谁也没有在意,当问题暴漏的时候,已经距离发版日期很近了,也就是说如果我们不能在一周内解决那就要放弃这个版本,或者放弃过春节。
小伙伴们都懵逼了,大家开会自查代码,优化性能,利用instument里的找问题。发现了很多代码写的不合理的地方,一一改正,这里我必须说一下,作为一个程序猿是要有猿类的觉悟的,可能我们生活中不太注意形象,作风邋遢随性,形象无法登大雅之堂,但是代码从我们手中写出来绝对不能乱七八糟,敷衍了事,相信每一个自律的程序员对自己的代码都有一定的要求,代码一定要遵从自己心胸中认定的最熟悉的规范。千万不能有个侥幸心理什么的,有个定律叫墨菲定律,意思是该发生的一定会发,出来混迟早要还的。
你少写一个判断,那么一定会有崩溃发生在哪里,影响着软件的质量。如果你少写两行注释,那么很久以后当你自己也读不懂这些代码的时候会从心里鄙视“他*的,这谁写的代码,这么乱”,全然想不起自己鄙视了自己一番。
说太多了,干货在这里。我们优化了许多代码以后,虽然有些改善,但是软件过一段时间后依然会卡顿无比,奇怪的是内存并未上涨,多次检查过发现并没有内存泄漏。
用instument里的 time profile工具查看发现CPU的占用率是随时间增加而逐渐上升的,于是小伙伴们一看不是常见问题纷纷束手无策。这个时候作为一个团队拿工资最高的家伙,解决这些疑难问题简直是义不容辞啊。于是我开始了解决问题的漫漫征程,之所以说称之为漫漫是因为解决问题一共花了3天,加上期间公司年会和部门年会的两天无心干活的话,那一共花了5天。
我思考了一下问题表现和可能发生的情况,发现这种情况app的内存不涨,CPU逐渐升高,那么问题一定是出在线程上,利用工具检查后印证了我的想法,我发现程序的线程数一直在涨涨涨,线程数一多,CPU处理主要逻辑时wait 的时间就会变长,cpu的运算被浪费在等待那些无用的增多的线程中去了。但是无奈的是我怎么看怎么看不出到底为何会涨。于是我要求小伙伴们自查代码以及互相review代码,看看有没有滥用线程的情况,查了整整一天直到晚上11点,最终无果。
第二天我无奈的发现我只有一种方法来找到我想要的东西,当所有的办法都不管用,那就只剩下排除法了。因为上一个版本还没有类似的问题,那么我只要注释掉这次迭代的代码,一点一点地打开注释,用排除法找到出问题的代码。悲催的是BUG虽然是必现的但是由于每次需要半小时才能出现明显的卡顿现象,那么排除法查找问题就变的奇慢无比,这真的是一种折磨啊!最后春节将至,小伙伴们开始陆续提前放假,我只好一个人孤独的寻找问题。
第三天当我找到出问题的代码后,我无论如何也不能相信,这段代码可以导致CPU利用率过高,而且这段代码根本看不出和线程有什么关系。我把代码贴出来就在下面,当时我第一反应是随机数函数和获取时间戳代码会耗时么?难道编译器出问题了?于是我又写了个Demo把这段代码分离出来测试,发现居然没有任何问题,不会出现任何导致CPU利用率上升的问题,而且他*的根本没用线程啊?
-(void)resetTimeSign
{
NSString * Factor =[NetConfiguration sharedNetConfig].factor;
NSString* timeStamp=[NSString stringWithFormat:@"%.0f",[NSDate date].timeIntervalSince1970*1000];
NSString *text= [NSString stringWithFormat:@"%@_%@_%@",Factor,self.userItem.userID,timeStamp];
[[NetConfiguration sharedNetConfig] addCommonRequestHeaderValue:timeStamp forKey:kDefault_Cleartext];
int arc =arc4random()%900+100;
NSString *cipherText = [NSString stringWithFormat:@"%d%@%d",arc,[[NetConfiguration sharedNetConfig] getReverseString:text],arc];
[[NetConfiguration sharedNetConfig] addCommonRequestHeaderValue:cipherText forKey:kDefault_Ciphertext];//--cyc
}
当我意识到可能是这个单列的问题时已经是第四天了,这时候年会过完,抽奖抽到了一个键盘,心说果然是程序猿的命啊!借着好心情我又分析了一下,这几行代码,如果有问题,肯定在这几个单列里。于是我终于开始从头看这个单列,看了一眼,顿时开始问候无数次某人女性先祖,单列是这么写的
+ (UserManager *)shareUserManager
{
if (shareUserManager != nil) {
return shareUserManager;
}
@synchronized(self)
{
if (!shareUserManager) {
shareUserManager = [[UserManager alloc] init];
}
}
return shareUserManager;
}
当看到@synchronized的时候,我终于明白出了啥事情,我们有个某线程轮询操作,每次都生成一条新线程,然后销毁,然而这个东西一调用和另一处锁造成了死锁,于是线程越来越多,无法销毁...... 死锁的线程参与分配CPU时间片,于是主线程wait的时间越来越多,越来越卡。
这年头单列早就不这么写了好吧,单列如今怎么写我也不在这里鳌述,这次的问题解决后我明白了一个道理,工具并不总是有用的,有时候走投无路的时候,排除法这种笨办法反而能解决问题,而且为了快速找到问题,我借鉴了二分查找的思想来做排除法,觉得能解决问题的办法就是好办法。总之黑猫白猫,能逮到老鼠就是好猫。当然解决问题一定有更好的办法,如果你看了以后觉得有好办法,希望大神可以不吝赐教!