簡單卻強大的線程監控工具 KKThreadMonitor :當線程過多或瞬間建立大量子線程(線程爆炸),控制檯就打印出全部的線程堆棧。便於分析形成子線程過多或線程爆炸的緣由。git
/******* 線程爆炸,控制檯打印以下: ********/
🔥💥💥💥💥💥一秒鐘開啓 28 條線程!💥💥💥💥💥🔥
👇👇👇👇👇👇👇堆棧👇👇👇👇👇👇👇
2020-04-12 12:36:29.270755+0800 BaiduIphoneVideo[55732:6928996]
當前線程數量:43
callStack of thread: 9219
libsystem_kernel.dylib 0x18022cc38 semaphore_wait_trap + 8
libdispatch.dylib 0x00003928 _dispatch_sema4_wait + 28
BaiduIphoneVideo 0x101285be0 -[TDXXXX脫敏🤣處理(去掉真實類名)XXXXager sendRequestWithBody:withURL:flag:] + 440
BaiduIphoneVideo 0x10128535c __29-[TDAXXXX脫敏🤣處理(去掉真實類名)XXXXnager sendMessage]_block_invoke + 804
libdispatch.dylib 0x00001dfc _dispatch_call_block_and_release + 32
... //省略中間線程堆棧
callStack of thread: 8707
libsystem_kernel.dylib 0x18022cc38 semaphore_wait_trap + 8
libdispatch.dylib 0x00003928 _dispatch_sema4_wait + 28
BaiduIphoneVideo 0x1026d2cd4 +[BaiduMobStatDeviceInfo testDeviceId] + 56
BaiduIphoneVideo 0x1026ca8a4 -[BaiduMobStatLogManager checkHeaderBeforeSendLog:] + 3384
BaiduIphoneVideo 0x1026cefdc -[BaiduMobStatLogManager _syncSendAllLog] + 536
BaiduIphoneVideo 0x1026c6398 __33-[BaiduMobStatController sendLog]_block_invoke + 56
libdispatch.dylib 0x00001dfc _dispatch_call_block_and_release + 32
... //省略中間線程堆棧
callStack of thread: 80387
libsystem_kernel.dylib 0x18024ea60 __open_nocancel + 8
libsystem_kernel.dylib 0x1802356e0 open$NOCANCEL + 20
BaiduIphoneVideo 0x101dcd488 -[XXXX脫敏🤣處理(去掉真實類名)XXXX recursiveCalculateAtPath:isAbnormal:isOutdated:needCheckIgnorePath:] + 1360
BaiduIphoneVideo 0x101dcd488 -[XXXX脫敏🤣處理(去掉真實類名)XXXX recursiveCalculateAtPath:isAbnormal:isOutdated:needCheckIgnorePath:] + 1360
BaiduIphoneVideo 0x101dcd488 -[XXXX脫敏🤣處理(去掉真實類名)XXXX recursiveCalculateAtPath:isAbnormal:isOutdated:needCheckIgnorePath:] + 1360
BaiduIphoneVideo 0x101dccd90 -[XXXX脫敏🤣處理(去掉真實類名)XXXX initWithOutdatedDays:abnormalFolderSize:abnormalFolderFileNumber:ignoreRelativePathes:checkSparseFile:sparseFileLeastDifferPercentage:sparseFileLeastDifferSize:visitors:] + 576
BaiduIphoneVideo 0x101dcd274 +[XXXX脫敏🤣處理(去掉真實類名)XXXX folderSizeAtPath:] + 72
BaiduIphoneVideo 0x101de3900 __56-[HMDToBCrashTracker(Environment) environmentInfoUpdate]_block_invoke_2 + 88
libdispatch.dylib 0x00001dfc _dispatch_call_block_and_release + 32
👆👆👆👆👆👆👆堆棧👆👆👆👆👆👆👆
複製代碼
有必定iOS開發基礎的,應該都知道不合理建立線程,是會形成性能問題的。而業務的積累和迭代過程,可能就不可避免形成子線程過多或線程爆炸等問題。因此咱們須要一個工具來監控子線程過多或線程爆炸等問題。網上沒有找到這樣的工具,只好本身動手了~github
合理使用多線程,將耗時操做放到子線程,能夠提升App的流暢。可是若是不加控制建立子線程,形成線程爆炸,會形成性能問題。不合理使用線程有以下問題:安全
因此咱們要合理使用線程,將整個App的子線程數量控制在合理的範圍內。如何合理使用,不是本文的討論範圍;本文介紹如何發現整個App線程數量過多和線程爆炸問題。bash
具體實現邏輯不到100行,很是簡單。 你們能夠下載代碼 KKThreadMonitor 研究。我講下核心邏輯:多線程
task_threads能夠獲取到整個App的線程數量。ide
kern_return_t task_threads
(
task_inspect_t target_task,
thread_act_array_t *act_list,
mach_msg_type_number_t *act_listCnt
);
複製代碼
最開始,我是想經過計時器定時調用task_threads函數,來獲取線程數量和增加速度。確實能夠經過這個方式來作,可是間隔多久調用(涉及精度),大量調用這個函數的性能問題等,都代表這不是一個好的方案。函數
很明顯,若是咱們知道了線程的建立函數跟銷燬函數,經過hook這兩個函數,是否是就能夠實時獲取到線程數量了。幸運的是,系統提供了這個函數:工具
//在#include <pthread/introspection.h>文件裏
/**
定義函數指針:pthread_introspection_hook_t
event : 線程處於的生命週期(下面枚舉了線程的4個生命週期)
thread :線程
addr :線程棧內存基址
size :線程棧內存可用大小
*/
typedef void (*pthread_introspection_hook_t)(unsigned int event,
pthread_t thread, void *addr, size_t size);
enum {
PTHREAD_INTROSPECTION_THREAD_CREATE = 1, //建立線程
PTHREAD_INTROSPECTION_THREAD_START, // 線程開始運行
PTHREAD_INTROSPECTION_THREAD_TERMINATE, //線程運行終止
PTHREAD_INTROSPECTION_THREAD_DESTROY, //銷燬線程
};
/**
看這個函數名,很像咱們平時hook函數同樣的。
返回值是上面聲明的pthread_introspection_hook_t函數指針:返回原線程生命週期函數。
參數也是函數指針:傳入的是咱們自定義的線程生命週期函數
*/
__attribute__((__nonnull__, __warn_unused_result__))
extern pthread_introspection_hook_t
pthread_introspection_hook_install(pthread_introspection_hook_t hook);
複製代碼
因此咱們能夠經過"hook"線程生命週期函數,來得到線程的新建跟銷燬。post
調用startMonitor函數,開始監控線程數量。在這個函數裏用global_semaphore來保證,task_threads獲取的線程數量,到hook完成,線程數量不會變化(加解鎖之間,沒有線程新建跟銷燬)。性能
+ (void)startMonitor
{
global_semaphore = dispatch_semaphore_create(1);
dispatch_semaphore_wait(global_semaphore, DISPATCH_TIME_FOREVER);
mach_msg_type_number_t count;
thread_act_array_t threads;
task_threads(mach_task_self(), &threads, &count);
threadCount = count; //加解鎖之間,保證線程的數量不變
old_pthread_introspection_hook_t = pthread_introspection_hook_install(kk_pthread_introspection_hook_t);
dispatch_semaphore_signal(global_semaphore);
...
}
複製代碼
//定時器每一秒都將線程增加數置0
+ (void)clearThreadCountIncrease
{
threadCountIncrease = 0;
}
void kk_pthread_introspection_hook_t(unsigned int event,
pthread_t thread, void *addr, size_t size)
{
...
//建立線程,則線程數量和線程增加數都加1
if (event == PTHREAD_INTROSPECTION_THREAD_CREATE) {
threadCount = threadCount + 1;
...
threadCountIncrease = threadCountIncrease + 1;
...
}
//銷燬線程,則線程數量和線程增加數都加1
else if (event == PTHREAD_INTROSPECTION_THREAD_DESTROY){
threadCount = threadCount - 1;
...
if (threadCountIncrease > 0) {
threadCountIncrease = threadCountIncrease - 1;
}
}
}
複製代碼
這裏我是用本身另一個庫KKCallStack。
如何打印線程堆棧,保留調用現場的博客不少,原理都是:回溯棧幀,得到函數調用地址,而後經過解析MachO文件得到函數名。具體過程能夠參考其它博客~