iOS執行緒生命週期的監控

歐陽大哥2013發表於2019-05-05

iOS系統通過Core Services層的Foundation框架提供基於OC語言的NSThread和NSOperationQueue類來實現對執行緒和執行緒池的管理和使用。同時也提供了一套基於C語言的GCD執行緒池函式庫來支援多執行緒的處理應用。這些高階的執行緒類或者函式的內部實現大部分最終都會呼叫POSIX標準中的pthread執行緒庫中的pthread_xxx系列函式(#include <pthread.h>)來完成執行緒的建立、執行、暫停、恢復、銷燬、結束等操作。使用者態下的執行緒建立通過系統呼叫到達核心態的BSD層並建立bsdthread物件,而BSD層則呼叫Mach層的ksthread物件來完成最終執行緒的建立和排程的。

執行緒架構圖

pthread庫中除了提供一系列標準的執行緒操作API外,還提供了一個用於監控執行緒建立、執行、結束、銷燬的內省函式(單詞introspection翻譯為內省,但我覺得叫攔截器可能更好一些)。這個函式定義在標頭檔案#include <pthread/introspection.h>中,函式的簽名為:


pthread_introspection_hook_t pthread_introspection_hook_install(pthread_introspection_hook_t hook)

複製程式碼

函式的作用是安裝一個回撥函式來掛鉤執行緒生命週期的四個過程。因此函式的入參是一個函式指標,返回的則是老的掛鉤函式的指標。回撥函式是一個格式為pthread_introspection_hook_t型別的函式,其格式定義如下:


typedef void (*pthread_introspection_hook_t)(unsigned int event, pthread_t thread, void *addr, size_t size);

複製程式碼

回撥函式的每個引數的意義如下:

event:指定執行緒所處的狀態。

thread: 執行緒的控制程式碼,每個pthread執行緒都由一個pthread_t型別控制程式碼來唯一標識。

addr: 為執行緒分配的棧記憶體的基地址。

size: 為執行緒分配的棧記憶體的尺寸。

上面說的每一個執行緒有建立、執行、終止、銷燬四個狀態,而event則是用來表示執行緒的四種狀態的值,它的值是如下列舉結構的某一個值:

enum {
	PTHREAD_INTROSPECTION_THREAD_CREATE = 1,  //建立
	PTHREAD_INTROSPECTION_THREAD_START,    //執行
	PTHREAD_INTROSPECTION_THREAD_TERMINATE,  //終止
	PTHREAD_INTROSPECTION_THREAD_DESTROY,  //銷燬
};
複製程式碼

需要注意的是在內省函式中設定回撥掛鉤函式後只會監控設定之後的所有執行緒狀態的變化。因此如果我們要監控整個應用生命週期的所有執行緒的狀態時,需要儘可能早的進行回撥函式的設定,比如可以在某個類的+load方法中,或者在某個全域性C++物件的建構函式中設定等等。

回撥掛鉤函式中的第二個引數thread是一個型別為pthread_t執行緒控制程式碼物件,這個物件的結構並沒有對外公開。但是因為pthread庫已經被蘋果開源:opensource.apple.com/source/libp… 。因此我們可以通過執行緒控制程式碼物件的內部定義來獲取關於執行緒的更多資訊。以方便我們能對執行緒的各種資料進行更加詳細的記錄。當然這裡我們需要考慮到執行緒控制程式碼的不同版本下的資料成員的問題。

最後我們實現一個簡單的在main函式內實現執行緒監控的程式碼示例:

#include <pthread/introspection.h>
#include <pthread.h>
#include <stdlib.h>
#include <stdio.h>

pthread_introspection_hook_t   g_oldpthread_introspection_hook = NULL;

void mypthread_introspection_hook(unsigned int event, pthread_t thread, void *addr, size_t size)
{
   __uint64_t threadid;
    pthread_threadid_np(thread, &threadid);
     
     printf("thread_id = %d,  addr = %p, size = %d\n", threadid, addr, size);
     switch (event)
     {
           case PTHREAD_INTROSPECTION_THREAD_CREATE:
              //dothing ..
              break;
           case PTHREAD_INTROSPECTION_THREAD_START:
             //dothing ..
              break;
           case  PTHREAD_INTROSPECTION_THREAD_TERMINATE:
             //dothing ..
              break;
           case PTHREAD_INTROSPECTION_THREAD_DESTROY:
             //dothing ..
              break;
      }

   //記得在最後或者開頭呼叫老的hook函式
  if (g_oldpthread_introspection_hook != NULL)
    g_oldpthread_introspection_hook(event, thread, addr, size);
}



int main(int argc, char *argv[])
{

   //註冊執行緒監控的回撥函式為mypthread_introspection_hook
   g_oldpthread_introspection_hook  = pthread_introspection_hook_install(mypthread_introspection_hook);

   @autoreleasepool {
        return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
    }
}

複製程式碼

你可以通過開原始碼中對pthread_t型別結構體的定義來獲取執行緒的更多資訊,但是要注意執行緒庫的版本資訊。

執行緒監控回撥函式中的程式碼應該儘可能的精簡和高效,包括官方的標頭檔案中也有一段說明(實際上是可以被appstore稽核通過的):

This should only be used for introspection and debugging tools. Do not rely on it in shipping code.


歡迎大家訪問歐陽大哥2013的github地址

相關文章