在iOS开发中,我们会经常开辟新的线程去做一些事,如何合理的开辟线程,在App开发阶段,监控线程的开辟数量,避免线上发生意外情况。
当线程过多或瞬间创建大量子线程(线程爆炸),就在控制台打印信息,并记录信息。
- 创建子线程过多,是会造成性能问题的,因为创建线程需要占用内存空间(默认的情况下,主线程占1M,子线程占用512KB)。
- 不合理创建和使用线程,容易引发数据一致性(线程安全)和死锁问题。
因为在iOS中基本上都是使用的pthread
,在Mach
层中thread_basic_info
结构体封装了单个线程的基本信息。
struct thread_basic_info {
time_value_t user_time; /* user run time */
time_value_t system_time; /* system run time */
integer_t cpu_usage; /* scaled cpu usage percentage */
policy_t policy; /* scheduling policy in effect */
integer_t run_state; /* run state (see below) */
integer_t flags; /* various flags (see below) */
integer_t suspend_count; /* suspend count for thread */
integer_t sleep_time; /* number of seconds that thread has been sleeping */
}
一个Mach Task
包含它的线程列表。内核提供了task_threads
API 调用获取指定 task 的线程列表,然后可以通过thread_info
API调用来查询指定线程的信息,在 thread_act.h
中有相关定义。
task_threads
将target_task
任务中的所有线程保存在act_list
数组中,act_listCnt
表示线程个数:
kern_return_t task_threads
(
task_t target_task,
thread_act_array_t *act_list,
mach_msg_type_number_t *act_listCnt
);
thread_info
结构如下
kern_return_t thread_info
(
thread_act_t target_act,
thread_flavor_t flavor, // 传入不同的宏定义获取不同的线程信息
thread_info_t thread_info_out, // 查询到的线程信息
mach_msg_type_number_t *thread_info_outCnt // 信息的大小
);
如果频繁调用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);
下面开始写一个Monitor
static pthread_introspection_hook_t original_pthread_introspection_hook_t = NULL;
/// 创建信号量
static dispatch_semaphore_t semaphore;
/// 线程总数
static int threadCount = 0;
/// 是否开启监控
static bool isMonitor = false;
/// 线程总数阈值
static int averageThreadCount = 40;
/// 线程在一定时间内新增数
static int newThreadCount = 0;
/// 线程在一定时间内新增阈值
static int newAverageThreadCount = 10;
/// 开启监控
+ (void)startMonitor{
// 创建信号量 最大并发数为1
semaphore = dispatch_semaphore_create(1);
// 等待
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
mach_msg_type_number_t count;
thread_act_array_t threads;
// 获取到count
task_threads(mach_task_self(), &threads, &count);
// 保证加锁的时候,线程数量不变
threadCount = count;
// 添加🪝钩子函数
original_pthread_introspection_hook_t = pthread_introspection_hook_install(kry_pthread_introspection_hook_t);
// 解锁 信号量+1
dispatch_semaphore_signal(semaphore);
// 开始监控
isMonitor = true;
// 开启一个定时器 检测每秒线程创建 然后通过clearNewThreadCount置位0
const char *queenIdentifier = dispatch_queue_get_label(DISPATCH_CURRENT_QUEUE_LABEL);
if (queenIdentifier == dispatch_queue_get_label(dispatch_get_main_queue())) {
[NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:@selector(clearNewThreadCount) userInfo:nil repeats:YES];
}else{
dispatch_async(dispatch_get_main_queue(), ^{
[NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:@selector(clearNewThreadCount) userInfo:nil repeats:YES];
});
}
}
// 当前线程总数
+ (int)currentThreadCount{
return threadCount;
}
void kry_pthread_introspection_hook_t(unsigned int event,
pthread_t thread, void *addr, size_t size){
// 正常调用原有逻辑
if (original_pthread_introspection_hook_t) {
original_pthread_introspection_hook_t(event,thread,addr,size);
}
// 开始记录
// 如果是创建线程,则线程的数量+1,新增数+1
if (event == PTHREAD_INTROSPECTION_THREAD_CREATE) {
threadCount +=1;
if (isMonitor && threadCount > averageThreadCount) {
// 总数 超过阈值 警告或者记录堆栈
kry_Log_CallStack(false, 0);
}
newThreadCount +=1;
if (isMonitor && newThreadCount > newAverageThreadCount) {
// 新增数 超过阈值 警告或者记录堆栈
kry_Log_CallStack(true, newThreadCount);
}
}
// 销毁线程,则线程数量-1,新增数-1
if (event == PTHREAD_INTROSPECTION_THREAD_DESTROY) {
threadCount -=1;
if (newThreadCount > 0 ) {
newThreadCount -=1;
}
}
}
void kry_Log_CallStack(bool isIncreaseLog, int num)
{
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
if (isIncreaseLog) {
printf("\n🔥一秒钟开启 %d 条线程!!!!\n", num);
}
// 可以记录堆栈信息
dispatch_semaphore_signal(semaphore);
}
+ (void)clearNewThreadCount{
newThreadCount = 0;
}
代码很简单,基本上就是把之前大佬们写的东西再写了一遍,都有详细注释,近期在看PCL的堆栈记录,后期会把堆栈记录完善上去