問題引出
最近在工作過程中账锹,遇到一個(gè)很有意思的bug,問題出現(xiàn)概率很小坷襟,很難復(fù)現(xiàn)奸柬,但是特別嚴(yán)重,直接導(dǎo)致用戶無法使用App婴程,屬于不可容忍的問題廓奕,因此必須解決。問題出現(xiàn)在Android-App跑壓測(cè)的過程中档叔,我們提供的第三方庫會(huì)突然停止工作桌粉,但是App的UI還是能正常的運(yùn)轉(zhuǎn),于是我以我的職業(yè)敏感性當(dāng)即做出判斷(害羞 <逃)蹲蒲,導(dǎo)致這個(gè)bug是因?yàn)槲覀兲峁┑膭?dòng)態(tài)庫中的消息線程被堵塞了番甩,從而導(dǎo)致整個(gè)庫無法工作。
通常情況下届搁,線程被堵塞住缘薛,有下面幾個(gè)原因:
- 執(zhí)行耗時(shí)任務(wù)(如網(wǎng)絡(luò)IO或者文件IO等,導(dǎo)致整個(gè)線程卡死)
- 死鎖(消息線程拿著A鎖卡睦,等待B鎖宴胧,數(shù)據(jù)處理線程拿著B鎖,等待A鎖表锻,相互等待恕齐,導(dǎo)致卡死)
- 死循環(huán)(程序卡在一個(gè)循環(huán),CPU狂轉(zhuǎn)瞬逊,無法跳出)
問題定位
首先第一個(gè)思路显歧,就是在App出現(xiàn)卡主問題后,導(dǎo)出所有線程的調(diào)用堆棧确镊,查看我們自己的庫線程士骤,看哪個(gè)線程哪個(gè)函數(shù)調(diào)用存在問題,取線程堆棧蕾域,通常有兩個(gè)方法:
- 在Android端拷肌,程序ANR后到旦,系統(tǒng)會(huì)保留該進(jìn)程的所有線程堆棧的traces文件。但是有個(gè)前提巨缘,只有主線程(UI線程)卡主的時(shí)候添忘,系統(tǒng)才會(huì)生成這個(gè)文件,其他子線程或者庫線程卡主是不會(huì)生成traces文件的若锁。
- 出現(xiàn)問題后搁骑,讓App強(qiáng)制crash,生成crash線程堆棧拴清。我們嘗試讓UI線程強(qiáng)制crash后靶病,卻只生成了UI線程的調(diào)用棧,沒有我們庫線程的信息口予。
抓調(diào)用棧的思路否定之后娄周,我們決定先進(jìn)一步縮小問題范圍,出現(xiàn)卡主問題后沪停,我們對(duì)Android手機(jī)執(zhí)行了如下命令煤辨,找出我們App所有線程的運(yùn)行狀況。
adb shell top -m 20 -t
巧合地發(fā)現(xiàn)每次采樣數(shù)據(jù)中木张,CPU使用率最高都是我們App進(jìn)程中的同一個(gè)線程众辨,正是我們動(dòng)態(tài)庫的消息線程,而該線程的使用率竟然比圖像繪制線程占比還要高了幾倍舷礼,很明顯的異常鹃彻,因此幾乎可以斷定是該線程導(dǎo)致的卡死問題,同時(shí)也可以排除死鎖的可能性妻献,因?yàn)樗梨i會(huì)讓線程wait住蛛株,不會(huì)過多的占用CPU,同時(shí)由于我們的AppIO操作很少育拨,也就排除了文件IO和網(wǎng)絡(luò)IO谨履,基本可以確認(rèn)是死循環(huán)導(dǎo)致。
那么這個(gè)線程到底是在哪個(gè)位置卡死呢熬丧?這時(shí)候最笨的方法笋粟,效果最明顯,【二分加Log法】析蝴,加log的位置以代碼行數(shù)進(jìn)行二分切隔害捕,如果第N行日志沒有輸出,那么肯定說明問題代碼在第N-1~N行之間闷畸,于是乎最終定位到了這一段神奇的死循環(huán)代碼問題尝盼。
void TransAngleTo360(double& dAngle)
{
while (dAngle < 0)
dAngle += 360;
while (dAngle > 360)
dAngle -= 360;
}
這段代碼,通過兩個(gè)while循環(huán)將輸入角度歸化到0~360度之間腾啥,問題就出在這個(gè)輸入值這里东涡。通過復(fù)現(xiàn)的日志發(fā)現(xiàn),代碼走到了異常分支倘待,對(duì)某個(gè)靜態(tài)數(shù)組 double arrAngele[8]疮跑,執(zhí)行了取下標(biāo)-1的操作,arrAngele[-1]凸舵,產(chǎn)生了一個(gè)未定義的極大值祖娘,8字節(jié)的double的取值范圍是:
負(fù)值取值范圍為 -1.79769313486231570E+308 到 -4.94065645841246544E-324;
正值取值范圍為 4.94065645841246544E-324 到 1.79769313486231570E+308啊奄。
如果將1.79e+308這個(gè)數(shù)傳給TransAngleTo360函數(shù)渐苏,那么函數(shù)需要執(zhí)行5e+305次循環(huán)才能結(jié)束,假設(shè)我們的計(jì)算機(jī)每秒能執(zhí)行10億次機(jī)器指令菇夸,那執(zhí)行完這個(gè)循環(huán)的時(shí)間琼富,大約是1e+207秒。庄新。鞠眉。。
問題解決
對(duì)于函數(shù)輸入?yún)?shù)择诈,進(jìn)行超大或超小值的范圍控制械蹋,很明顯這個(gè)方法只是暫時(shí)的補(bǔ)丁,且是在你知道入?yún)⒋笮〉那疤嵯滦呱郑龅南拗苹└辏绻淮_定入?yún)⒎秶芸赡軙?huì)出問題荷科。
那么更好的做法其實(shí)是這樣的唯咬,先看整數(shù)部分有多少個(gè)360,記為cnt步做,然后用原有角度減去cnt*360即可副渴,為負(fù)數(shù)的時(shí)候?qū)?yīng)處理下就行。
void TransAngleTo360(double& dAngle)
{
int cnt = dAngle/360;
if (time >= 0)
dAngle = dAngle - cnt*360;
else
dAngle = dAngle - (cnt - 1)*360;
}