iOS線程生命周期的監控

2021-02-13 iOS開發

作者丨歐陽大哥2013
https://www.jianshu.com/p/813ba526204b

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庫已經被蘋果開源:https://opensource.apple.com/source/libpthread/  

因此我們可以通過線程句柄對象的內部定義來獲取關於線程的更多信息。以方便我們能對線程的各種數據進行更加詳細的記錄。當然這裡我們需要考慮到線程句柄的不同版本下的數據成員的問題。

最後我們實現一個簡單的在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:
              
              break;
           case PTHREAD_INTROSPECTION_THREAD_START:
             
              break;
           case  PTHREAD_INTROSPECTION_THREAD_TERMINATE:
             
              break;
           case PTHREAD_INTROSPECTION_THREAD_DESTROY:
             
              break;
      }

   
  if (g_oldpthread_introspection_hook != NULL)
    g_oldpthread_introspection_hook(event, thread, addr, size);
}



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

   
   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.

 推薦↓↓↓ 

涵蓋:程式設計師大咖、源碼共讀、程式設計師共讀、數據結構與算法、黑客技術和網絡安全、大數據科技、編程前端、Java、Python、Web編程開發、Android、iOS開發、Linux、資料庫研發、幽默程式設計師等。

相關焦點

  • 黑馬程式設計師:多線程的生命周期以及執行順序等習題練習
    習題總結:本文的習題主要是圍繞線程是如何創建的,線程的生命周期和執行順序,控制線程的啟動和掛起,以及如何正常結束線程的知識點。通過額額習題的掌握,應該能夠對多線程技術有較為深入的了解,並對多線程的創建、調度、同步以及通信操作能做到熟練掌握。需要獲取答案的小夥伴請轉發加評論。
  • iOS多線程全套:線程生命周期,多線程的四種解決方案,線程安全問題,GCD的使用,NSOperation的使用(下)
    ,沒有開啟新線程。,沒有開啟新線程。同樣的,NSBlockOperation可以配合隊列NSOperationQueue來實現多線程。,而運用addExecutionBlock加入的任務是在子線程執行的。
  • 跟我學Java編程—理解線程生命周期及Thread類的主要方法
    通過前面兩節的學習,我們對線程有了基本認識。了解了線程和進程的區別以及線程的使用方法和場景。本節學習線程的狀態和Thread類的主要方法。1、線程的生命周期及狀態線程從創建到消亡,要經歷創建(new)、就緒(runnable)、運行(running)、阻塞(blocked)、等待(waiting)、消亡(dead)等若干狀態,其中運行、阻塞、等待狀態之間會互相轉換。
  • 【Android基礎】Activity生命周期
    當你查看一個Java項目時你肯定想到的是從它的入口函數main()開始,一步一步的深入去看這個項目的邏輯是怎麼展開的,查看Android項目也不例外,但是它的入口不是main函數,而是主Activity類。
  • servlet生命周期
    1.servlet是運行在服務端的java程序2.servlet的生命周期主要有三個方法:init()初始化階段load-on-startup>在Servlet容器啟動後,客戶首次向Servlet發送請求Servlet類文件被更新後,重新裝載處理客戶端請求階段:每收到一個客戶端請求,伺服器就會產生一個新的線程去處理
  • servlet生命周期一共分幾步?
    Servlet生命周期分為三個階段:初始化階段、響應客戶請求階段、終止階段,其具體操作如下圖所示:首先簡單解釋一下Servlet接收和響應客戶請求的過程,首先客戶發送一個請求,Servlet是調用service()方法對請求進行響應 的,通過原始碼可見
  • Java中的線程(狀態轉換和線程間通信)
    Java中的線程(狀態轉換和線程間通信)什麼是線程線程是作業系統調度的最小單元,在一個進程中可以創建多個線程,進程中的線程可以共享資源,但是每個線程都有自己的線程棧空間。Java運行是從main方法開始執行,會生成一個名為main線程。Java中的線程Java中的線程Thread類,是用來創建和啟動線程。使用方法Thread.start()來啟動一個線程。實現一個線程通常有這麼幾個方法。1、繼承Thread類,重寫run方法。執行start方法啟動線程。
  • java線程的6中狀態,你了解嗎?
    Java 線程在其運行的生命周期中一個有6中狀態。線程的6種狀態線程在生命周期中並不是固定處於某一個狀態而是隨著代碼的執行在不同狀態之間切換。Java 線程狀態變遷如下圖所示java線程狀態切換由上圖可以看出:一個線程在其被創建之後它將處於 NEW(新建)狀態,調用runnable.start()方法後開始運行,
  • Netty的EventLoop和線程模型
    線程模型指定了os、程式語言、框架或應用程式的上下文中的線程管理的關鍵方面。如何、何時創建線程將對應用程式代碼執行產生顯著影響,開發人員必須理解不同模型之間的權衡。而 Netty 的線程模型強大又易用,正如 Netty 的宗旨:簡化你的應用程式代碼,同時最大限度提高性能和可維護性。
  • 圖解 Laravel 請求的完整生命周期
    Laravel是一套簡潔的PHP Web開發框架(PHP Web Framework),今天,我們就來了解 一下Laravel 的生命周期。在此之前,我們先回顧一下PHP 的生命周期。PHP 的生命周期生命周期當我們請求一個php文件時,PHP 為了完成這次請求,會發生5個階段的生命周期切換:模塊初始化(MINIT),即調用 php.ini 中指明的擴展的初始化函數進行初始化工作,如 mysql 擴展。
  • 深度解析iOS應用程式的生命周期
    View Controller有一個view屬性是view層次結構中的根view,你可以添加子view來構建複雜的view;controller有一些viewDidLoad、viewWillAppear等方法來管理view的生命周期;由於它繼承UIResponder,所有還會響應和處理用戶事件。data model對象主要用來存儲數據。
  • 第285天:React生命周期
    React生命周期React的生命周期從廣義上分為掛載、渲染、卸載三個階段,在React的整個生命周期中提供很多鉤子函數在生命周期的不同時刻調用
  • 【Linux公開課】線程ID、創建與終止
    每個線程都有從創建到終止的生命周期。 文員也能看懂學通的《嵌入式Linux開發教程》正式開始免費連載啦!致遠電子微信公眾平臺【獨家首發】。在這個教程中,你看不到自我陶醉的炫技,內容通俗易懂,實用夠用為先。本教程凝聚了ZLG致遠電子嵌入式工程師的心血,轉載請【註明出處】。
  • Linux系統監控工具atop
    >clones 表示在監控周期(默認10s)內 clone() 系統調用次數linux 中進程有兩種 sleep 狀態:interruptible sleep: 進程接收系統信號,可以被系統信號中斷uninterruptible sleep
  • 微服務手冊:API接口9個生命節點,構建全生命周期管理
    生命周期在我們軟體行業的領域裡面,每一個軟體都是有生命周期的,從最開始的需求調研,需求設計,架構設計,軟體研發,測試,上線,試運行,運行到最後業務上,技術上跟不上時代的發展,被新來的技術人員嫌棄,後面的業務部門拋棄,至此開始結束最後到下線,這個系統就算結束了他們的生命周期。
  • Java之線程狀態的簡單介紹
    各位小夥伴們大家好,在之前的文章中,小編介紹了線程安全問題的相關知識,這次小編要針對線程的狀態做一些相關介紹。當線程被創建並啟動以後,它既不是一啟動線程就進入了執行狀態,也不是一直處於執行狀態。在線程的生命周期中,有六種線程狀態:new(新建):線程剛剛被創建,但是並未啟動,還沒有調用start方法。Runnable(可運行):線程可以在Java虛擬機中運行的狀態,可能正在運行自己的代碼,也可能沒有,這取決於作業系統處理器。
  • 線程數設置多少更合適?
    此時假設我們設置的線程數量是 CPU 核心數的 2 倍,因為計算任務非常重,會佔用大量的 CPU 資源,所以這時 CPU 的每個核心工作基本都是滿負荷的,而我們又設置了過多的線程,每個線程都想去利用 CPU 資源來執行自己的任務,這就會造成不必要的上下文切換,此時線程數的增多並沒有讓性能提升,反而由於線程數量過多會導致性能下降。
  • 初學Java多線程:向線程傳遞數據的三種方法
    初學Java多線程:向線程傳遞數據的三種方法 本文講述在學習Java多線程中需要學習的向線程傳遞數據的三種方法。由於線程的運行和結束是不可預料的,因此,在傳遞和返回數據時就無法象函數一樣通過函數參數和return語句來返回數據。
  • 「011期」JavaSE面試題(十一):多線程(1)
    start()方法是一個 native 方法,它將啟動一個新線程,並執行 run()方法。這種方式實現多線程很簡單,通過自己的類直接 extend Thread,並重寫 run()方法,就可以啟動新線程並執行自己定義的 run()方法。例如:繼承 Thread 類實現多線程,並在合適的地方啟動線程。
  • Java經典面試題:一個線程兩次調用start()方法會出現什麼情況?
    今天我要問你的問題是,一個線程兩次調用start()方法會出現什麼情況?談談線程的生命周期和狀態轉移。典型回答Java的線程是不允許啟動兩次的,第二次調用必然會拋出IllegalThreadStateException,這是一種運行時異常,多次調用start被認為是編程錯誤。