iOS线程数量监控工具
2020/4/20 23:18:02
本文主要是介绍iOS线程数量监控工具,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!
简单却强大的线程监控工具 KKThreadMonitor :当线程过多或瞬间创建大量子线程(线程爆炸),控制台就打印出所有的线程堆栈。便于分析造成子线程过多或线程爆炸的原因。
/******* 线程爆炸,控制台打印如下: ********/ 🔥💥💥💥💥💥一秒钟开启 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开发基础的,应该都知道不合理创建线程,是会造成性能问题的。而业务的积累和迭代过程,可能就不可避免造成子线程过多或线程爆炸等问题。所以我们需要一个工具来监控子线程过多或线程爆炸等问题。网上没有找到这样的工具,只好自己动手了~
不合理创建/使用线程造成的问题
合理使用多线程,将耗时操作放到子线程,可以提高App的流畅。但是如果不加控制创建子线程,造成线程爆炸,会造成性能问题。不合理使用线程有如下问题:
- 创建子线程过多,是会造成性能问题的,因为创建线程需要占用内存空间(默认的情况下,主线程占1M,子线程占用512KB)。
- 不合理创建和使用线程,容易引发数据一致性(线程安全)和死锁问题。
所以我们要合理使用线程,将整个App的子线程数量控制在合理的范围内。如何合理使用,不是本文的讨论范围;本文介绍如何发现整个App线程数量过多和线程爆炸问题。
核心逻辑
具体实现逻辑不到100行,非常简单。 大家可以下载代码 KKThreadMonitor 研究。我讲下核心逻辑:
如何获取线程数量
task_threads可以获取到整个App的线程数量。
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"线程生命周期函数,来获得线程的新建跟销毁。
用锁保证线程安全
调用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文件获得函数名。具体过程可以参考其它博客~
参考
- github.com/kstenerud/K…
- wereadteam.github.io/2016/05/03/…
- juejin.im/post/5cce3a…
这篇关于iOS线程数量监控工具的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!
- 2022-10-05Swift语法学习--基于协议进行网络请求
- 2022-08-17Apple开发_Swift语言地标注释
- 2022-07-24Swift 初见
- 2022-05-22SwiftUI App 支持多语种 All In One
- 2022-05-10SwiftUI 组件参数简写 All In One
- 2022-04-14SwiftUI 学习笔记
- 2022-02-23Swift 文件夹和文件操作
- 2022-02-17Swift中使用KVO
- 2022-02-08Swift 汇编 String array
- 2022-01-30SwiftUI3.0页面反向传值