筆記-多線程底層初探

線程與進程

線程的定義

  • 線程是進程的基本執行單元,一個進程的全部任務都在線程中執行
  • 進程想要執行任務,必須得有線程,進程至少要有一條線程
  • 程序啓動會默認開啓一條線程,這條線程被稱爲主線程或UI線程

進程的定義

  • 進程是指在系統中正在運行的一個應用程序
  • 每一個進程之間是獨立的,每一個進程均運行在其專用的且受保護的內存

線程與進程的關係

  • 地址空間:同一進程的線程共享本進程的地址空間,而進程之間則是獨立的地址空間
  • 資源擁有:同一進程內的線程共享本進程的資源如內存、I/O、cpu等,可是進程之間的資源是獨立的
  • 執行過程:每一個獨立的進程有一個程序運行的入口、順序執行序列和程序入口。可是線程不能獨立執行,必須依存在應用程序中,由應用程序提供多個線程執行控制
  • 線程是處理器調度的基本單位,可是進程不是
  • 一個進程崩潰後,在保護模式下不會對其餘進程產生影響,可是一個線程崩潰整個進程都死掉。因此多進程要比多線程健壯
  • 進程切換時,消耗的資源大,效率高。因此涉及到頻繁的切換時,使用線程要好於進程。一樣若是要求同時進行而且又要共享某些變量的併發操做,只能用線程不能用進程

多線程的原理

原理

CPU在單位時間片裏快速在各線程之間的切換安全

意義

優勢:bash

  • 能適當提升程序的執行效率
  • 能適當提升資源的利用率(CPU,內存)
  • 線程上的任務執行完成後,線程會自動銷燬

缺點:多線程

  • 開啓線程須要佔用必定的內存空間(默認狀況下,每個線程都佔 512 KB)
  • 若是開啓大量的線程,會佔用大量的內存空間,下降程序的性能
  • 線程越多,CPU 在調用線程上的開銷就越大
  • 程序設計更加複雜,好比線程間的通訊、多線程的數據共享

線程的生命週期

  • new 新建一個線程
  • start 開始一個線程,線程進入就緒(runnable)狀態
  • CPU調度當前線程,進入運行(Running)狀態
  • Running狀態以後會出現幾種現象
  • 正常執行任務完畢,強制退出;受時間片的影響,切換到其餘線程;調用sleep或等待同步鎖,從可調度線程池中移除,進入阻塞(Blocked)狀態
  • sleep到時,獲取到同步鎖,從新添加回可調度線程池,再次進入就緒(Runnable)狀態

注意:start操做不可重複,當CPU調度當前線程,進入Running狀態時,這裏存在一個可調度線程池,會進行一系列的判斷,若是線程池裏有當前線程,會直接執行線程,若是沒有,則判斷當前線程池的大小是否小於核心線程池的大小,若是小於,則建立一個新的線程來執行;若是大於,則等待被加入到隊列等,具體能夠參考下面的線程池工做原理。併發

具體代碼看下線程的生命週期:oop

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    // 根據狀態來改變 - 線程
    if (self.t == nil) {
        // new 新建
        self.t = [[NSThread alloc] initWithTarget:self.p selector:@selector(testThreadStatus) object:@100];
        // 2. 啓動線程start - runnable
        [self.t start];
        self.t.name = @"學習線程";
    }
}
 
 - (void)testThreadStatus {
    // running
    for (int i = 0; i<10; i++) {
        // blocked
        if (i == 2) {
            sleep(1);
        }
    }
    [self.t cancel];
}
複製代碼

流程圖: 性能

線程池工做原理

在線程的生命週期中,須要考慮到當前的可調度線程池。 首先須要判斷當前線程池的大小是否小於核心線程池大小,若是是小於,則直接建立線程去執行任務;若是是大於,則沒有能力開闢新的線程去執行任務,只能依賴現有的線程,則須要判斷工做隊列是否已經滿了,若是沒有滿,則將任務push到隊列,執行任務;若是滿了,判斷線程池裏的線程是否都工做,若是沒有,則利用線程去執行任務;若是都在工做,則進入飽和策略。學習

飽和策略:ui

  • Abort策略(停止策略)默認策略,新任務提交時直接拋出未檢查的異常RejectedExecutionException,該異常可由調用者捕獲
  • Discard策略(拋棄任務)新提交的任務被拋棄
  • DiscardOldest策略(拋棄最舊的)隊列的是「隊頭」的任務,而後嘗試提交新的任務。(不適合工做隊列爲優先隊列場景)
  • CallerRuns策略(調用者運行)爲調節機制,既不拋棄任務也不拋出異常,而是將某些任務回退到調用者。不會在線程池的線程中執行新的任務,而是在調用exector的線程中運行新的任務

保證線程的安全,提升性能,具體可參考iOS的鎖spa

線程和runloop的關係

  • runloop與線程是一一對應的,一個runloop對應一個核心的線程,爲何說是核心的,是由於runloop是能夠嵌套的,可是核心的只能有一個,他們的關係保存在一個全局的字典裏
  • runloop是來管理線程的,當線程的runloop被開啓後,線程會在執行完任務後進入休眠狀態,有了任務就會被喚醒去執行任務
  • runloop在第一次獲取時被建立,在線程結束時被銷燬
  • 對於主線程來講,runloop在程序一啓動就默認建立好了
  • 對於子線程來講,runloop是懶加載的,只有當咱們使用的時候纔會建立,因此在子線程用定時器要注意:確保子線程的runloop被建立,否則定時器不會回調
相關文章
相關標籤/搜索