iOS之线程数量监控

918 阅读3分钟

在iOS开发中,我们会经常开辟新的线程去做一些事,如何合理的开辟线程,在App开发阶段,监控线程的开辟数量,避免线上发生意外情况。

当线程过多或瞬间创建大量子线程(线程爆炸),就在控制台打印信息,并记录信息。

  1. 创建子线程过多,是会造成性能问题的,因为创建线程需要占用内存空间(默认的情况下,主线程占1M,子线程占用512KB)。
  1. 不合理创建和使用线程,容易引发数据一致性(线程安全)和死锁问题。

因为在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_threadstarget_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的堆栈记录,后期会把堆栈记录完善上去

参考文献

APP性能检测方案汇总

iOS线程数量监控工具