iOS多線程之超實用理論+demo演示(可下載)

背景簡介

     在初學iOS相關知識過程當中,大多都對多線程有些恐懼的內心,同時感受工做中用上的機率不大。可是若是平時很少積累並學透多線程,當工做中真的須要用到的時候,就極可能簡單百度後把一些知識點稀裏糊塗地就用到工做中了,卻不知裏面有不少的坑,也有不少技巧須要在理論上先作了解,再結合實戰,進一步去體會多線程的魅力和強大。html

     接下來,就對多線程來源的背景進行簡單的介紹:ios

     在計算的早期,計算機能夠執行的最大工做量是由 CPU 的時鐘速度決定的。可是隨着技術的進步和處理器設計的緊湊化,熱量和其餘物理約束開始限制處理器的最大時鐘速度。所以,芯片製造商尋找其餘方法來提升芯片的整體性能。他們決定的解決方案是增長每一個芯片上的處理器核心數量。經過增長內核的數量,一個單獨的芯片能夠每秒執行更多的指令,而不用增長 CPU 的速度或改變芯片的大小或熱特性。惟一的問題是如何利用額外的內核。git

     應用程序使用多核的傳統方法是建立多個線程。與依賴線程不一樣,iOS 採用異步設計方法來解決併發問題。一般,這項工做涉及獲取一個後臺線程,在該線程上啓動所需的任務,而後在任務完成時向調用方發送通知(一般經過一個回調函數)。github

     iOS 提供了一些技術,容許您異步執行任何任務,而無需本身管理線程。異步啓動任務的技術之一是 Grand Central Dispatch (GCD)。這種技術採用線程管理代碼,並將該代碼移動到系統級別。您所要作的就是定義要執行的任務,並將它們添加到適當的分派隊列中。GCD 負責建立所需的線程,並安排任務在這些線程上運行。因爲線程管理如今是系統的一部分,GCD 提供了任務管理和執行的總體方法,比傳統線程提供了更高的效率。編程

     OperationQueue(操做隊列,api 類名爲 NSOperationQueue )是 Objective-C 對象,是對 GCD 的封裝。其做用很是相似於分派隊列。您定義要執行的任務,而後將它們添加到 OperationQueue 中, OperationQueue 處理這些任務的調度和執行。與 GCD 同樣, OperationQueue 爲您處理全部線程管理,確保在系統上儘量快速有效地執行任務。swift

     接下來,就對如今工做中經常使用的這兩種技術進行比較和實例解析。api

GCD、OperationQueue 對比

核心理念

  • GCD的核心概念:將 任務(block) 添加到隊列,而且指定執行任務的函數。
  • NSOperation 的核心概念:把 操做(異步) 添加到 隊列。

區別

  • GCD:安全

    • 將任務(block)添加到隊列(串行/併發/主隊列),而且指定任務執行的函數(同步/異步)
    • GCD是底層的C語言構成的API
    • iOS 4.0 推出的,針對多核處理器的併發技術
    • 在隊列中執行的是由 block 構成的任務,這是一個輕量級的數據結構
    • 要中止已經加入 queue 的 block 須要寫複雜的代碼
    • 須要經過 Barrier(dispatch_barrier_async)或者同步任務設置任務之間的依賴關係
    • 只能設置隊列的優先級
    • 高級功能:
      dispatch_once_t(一次性執行, 多線程安全);
      dispatch_after(延遲);
      dispatch_group(調度組);
      dispatch_semaphore(信號量);
      dispatch_apply(優化順序不敏感大致量for循環);
  • OperationQueue:網絡

    • OC 框架,更加面向對象,是對 GCD 的封裝。數據結構

    • iOS 2.0 推出的,蘋果推出 GCD 以後,對 NSOperation 的底層進行了所有重寫。

    • 能夠設置隊列中每個操做的 QOS() 隊列的總體 QOS

    • 操做相關
      Operation做爲一個對象,爲咱們提供了更多的選擇:
      任務依賴(addDependency),能夠跨隊列設置操做的依賴關係;
      在隊列中的優先級(queuePriority)
      服務質量(qualityOfService, iOS8+);
      完成回調(void (^completionBlock)(void)

    • 隊列相關
      服務質量(qualityOfService, iOS8+);
      最大併發操做數(maxConcurrentOperationCount),GCD 不易實現;
      暫停/繼續(suspended);
      取消全部操做(cancelAllOperations);
      KVO 監聽隊列任務執行進度(progress, iOS13+);

     接下來經過文字,結合實踐代碼(工程連接在文末)和運行效果 gif 圖對部分功能進行分析。

GCD

隊列

串行隊列(Serial Queues)

     串行隊列中的任務按順序執行;可是不一樣串行隊列間沒有任何約束; 多個串行隊列同時執行時,不一樣隊列中任務執行是併發的效果。好比:火車站買票能夠有多個賣票口,可是每一個排的隊都是串行隊列,總體併發,單線串行。

     注意防坑:串行隊列建立的位置。好比下面代碼示例中:在for循環內部建立時,每一個循環都是建立一個新的串行隊列,裏面只裝一個任務,多個串行隊列,結果總體上是併發的效果。想要串行效果,必須在for循環外部建立串行隊列。

     串行隊列適合管理共享資源。保證了順序訪問,杜絕了資源競爭。

      代碼示例:

private func serialExcuteByGCD(){
        let lArr : [UIImageView] = [imageView1, imageView2, imageView3, imageView4]
        
        //串行隊列,異步執行時,只開一個子線程
        let serialQ = DispatchQueue.init(label: "com.companyName.serial.downImage")
        
        for i in 0..<lArr.count{
            let lImgV = lArr[i]
            
            //清空舊圖片
            lImgV.image = nil
            
         //注意,防坑:串行隊列建立的位置,在這建立時,每一個循環都是一個新的串行隊列,裏面只裝一個任務,多個串行隊列,總體上是並行的效果。
            //            let serialQ = DispatchQueue.init(label: "com.companyName.serial.downImage")
            
            serialQ.async {
                
                print("第\(i)個 開始,%@",Thread.current)
                Downloader.downloadImageWithURLStr(urlStr: imageURLs[i]) { (img) in
                    let lImgV = lArr[i]
                    
                    print("第\(i)個 結束")
                    DispatchQueue.main.async {
                        print("第\(i)個 切到主線程更新圖片")
                        lImgV.image = img
                    }
                    if nil == img{
                        print("第\(i+1)個img is nil")
                    }
                }
            }
        }
    }

gif 效果圖:

serialGCD
圖中下載時可順利拖動滾動條,是爲了說明下載在子線程,不影響UI交互

log:

第0個 開始
第0個 結束
第1個 開始
第0個 更新圖片
第1個 結束
第2個 開始
第1個 更新圖片
第2個 結束
第3個 開始
第2個 更新圖片
第3個 結束
第3個 更新圖片

      由 log 可知: GCD 切到主線程也須要時間,切換完成以前,指令可能已經執行到下個循環了。可是看起來圖片仍是依次下載完成和顯示的,由於每一張圖切到主線程顯示都須要時間。

併發隊列(Concurrent Queues)

     併發隊列依舊保證中任務按加入的前後順序開始(FIFO),可是沒法知道執行順序,執行時長和某一時刻的任務數。按 FIFO 開始後,他們之間不會相互等待。

     好比:提交了 #1,#2,#3 任務到併發隊列,開始的順序是 #1,#2,#3。#2 和 #3 雖然開始的比 #1 晚,可是可能比 #1 執行結束的還要早。任務的執行是由系統決定的,因此執行時長和結束時間都沒法肯定。

     須要用到併發隊列時,強烈建議 使用系統自帶的四種全局隊列之一。可是,當你須要使用 barrier 對隊列中任務進行柵欄時,只能使用自定義併發隊列。

Use a barrier to synchronize the execution of one or more tasks in your dispatch queue. When you add a barrier to a concurrent dispatch queue, the queue delays the execution of the barrier block (and any tasks submitted after the barrier) until all previously submitted tasks finish executing. After the previous tasks finish executing, the queue executes the barrier block by itself. Once the barrier block finishes, the queue resumes its normal execution behavior.

     對比:barrier 和鎖的區別

  • 依賴對象不一樣,barrier 依賴的對象是自定義併發隊列,鎖操做依賴的對象是線程。
  • 做用不一樣,barrier 起到自定義併發隊列中柵欄的做用;鎖起到多線程操做時防止資源競爭的做用。

      代碼示例:

private func concurrentExcuteByGCD(){
        let lArr : [UIImageView] = [imageView1, imageView2, imageView3, imageView4]
        
        for i in 0..<lArr.count{
            let lImgV = lArr[i]
            
            //清空舊圖片
            lImgV.image = nil
            
            //並行隊列:圖片下載任務按順序開始,可是是並行執行,不會相互等待,任務結束和圖片顯示順序是無序的,多個子線程同時執行,性能更佳。
            let lConQ = DispatchQueue.init(label: "cusQueue", qos: .background, attributes: .concurrent)
            lConQ.async {
                print("第\(i)個開始,%@", Thread.current)
                Downloader.downloadImageWithURLStr(urlStr: imageURLs[i]) { (img) in
                    let lImgV = lArr[i]
                      print("第\(i)個結束")
                    DispatchQueue.main.async {
                        lImgV.image = img
                    }
                    if nil == img{
                        print("第\(i+1)個img is nil")
                    }
                }
            }
        }
    }

gif 效果圖:
conGCD

log:

第0個開始,%@ <NSThread: 0x600002de2e00>{number = 4, name = (null)}
第1個開始,%@ <NSThread: 0x600002dc65c0>{number = 6, name = (null)}
第2個開始,%@ <NSThread: 0x600002ddc8c0>{number = 8, name = (null)}
第3個開始,%@ <NSThread: 0x600002d0c8c0>{number = 7, name = (null)}
第0個結束
第3個結束
第1個結束
第2個結束

串行、併發隊列對比圖

gcd-cheatsheet

注意事項

  • 不管串行仍是併發隊列,都是 FIFO ;
    通常建立 任務(blocks)和加任務到隊列是在主線程,可是任務執行通常是在其餘線程(asyc)。須要刷新 UI 時,若是當前再也不主線程,須要切回主線程執行。當不肯定當前線程是否在主線程時,可使用下面代碼:
/**
 Submits a block for asynchronous execution on a main queue and returns immediately.
 */
static inline void dispatch_async_on_main_queue(void (^block)()) {
    if (NSThread.isMainThread) {
        block();
    } else {
        dispatch_async(dispatch_get_main_queue(), block);
    }
}
  • 主隊列是串行隊列,每一個時間點只能有一個任務執行,所以若是耗時操做放到主隊列,會致使界面卡頓。

  • 系統提供一個串行主隊列,4個 不一樣優先級的全局隊列。
    用 dispatch_get_global_queue 方法獲取全局隊列時,第一個參數有 4 種類型可選:

    • DISPATCH_QUEUE_PRIORITY_HIGH
    • DISPATCH_QUEUE_PRIORITY_DEFAULT
    • DISPATCH_QUEUE_PRIORITY_LOW
    • DISPATCH_QUEUE_PRIORITY_BACKGROUND
  • 串行隊列異步執行時,切到主線程刷 UI 也須要時間,切換完成以前,指令可能已經執行到下個循環了。可是看起來圖片仍是依次下載完成和顯示的,由於每一張圖切到主線程顯示都須要時間。詳見 demo 示例。

  • iOS8 以後,若是須要添加可被取消的任務,可使用 DispatchWorkItem 類,此類有 cancel 方法。

  • 應該避免建立大量的串行隊列,若是但願併發執行大量任務,請將它們提交給全局併發隊列之一。建立串行隊列時,請嘗試爲每一個隊列肯定一個用途,例如保護資源或同步應用程序的某些關鍵行爲(如藍牙檢測結果須要有序處理的邏輯)。

block(塊)相關

     調度隊列複製添加到它們中的塊,並在執行完成時釋放塊。
     雖然隊列在執行小任務時比原始線程更有效,可是建立塊並在隊列上執行它們仍然存在開銷。若是一個塊執行的工做量太少,那麼內聯執行它可能比將它分派到隊列中要便宜得多。判斷一個塊是否工做量太少的方法是使用性能工具爲每一個路徑收集度量數據並進行比較。
     您可能但願將 block 的部分代碼包含在 @autoreleasepool 中,以處理這些對象的內存管理。儘管 GCD 調度隊列擁有本身的自動釋放池,但它們不能保證這些池什麼時候耗盡。若是您的應用程序是內存受限的,那麼建立您本身的自動釋放池可讓您以更有規律的間隔釋放自動釋放對象的內存。

dispatch_after

     dispatch_after 函數並非在指定時間以後纔開始執行處理,而是在指定時間以後將任務追加到隊列中。這個時間並非絕對準確的。
  代碼示例:

dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        NSLog(@"2s後執行");
    });

dispatch_semaphore

      在多線程訪問可變變量時,是非線程安全的。可能致使程序崩潰。此時,能夠經過使用信號量(semaphore)技術,保證多線程處理某段代碼時,後面線程等待前面線程執行,保證了多線程的安全性。使用方法記兩個就好了,一個是wait(dispatch_semaphore_wait),一個是signal(dispatch_semaphore_signal)。

具體請參考文章Semaphore回顧

dispatch_apply

     當每次迭代中執行工做與其餘全部迭代中執行的工做不一樣,且每一個循環完成的順序不重要時,能夠用 dispatch_apply 函數替換循環。注意:替換後, dispatch_apply 函數總體上是同步執行,內部 block 的執行類型(串行/併發)由隊列類型決定,可是串行隊列易死鎖,建議用併發隊列。

原循環:

for (i = 0; i < count; i++) {
   printf("%u\n",i);
}
printf("done");

優化後:

dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
 
 //count 是迭代的總次數。
dispatch_apply(count, queue, ^(size_t i) {
   printf("%u\n",i);
});

//一樣在上面循環結束後才調用。
printf("done");

     您應該確保您的任務代碼在每次迭代中完成合理數量的工做。與您分派到隊列的任何塊或函數同樣,調度該代碼以便執行會帶來開銷。若是循環的每次迭代只執行少許的工做,那麼調度代碼的開銷可能會超過將代碼分派到隊列可能帶來的性能優點。若是您在測試期間發現這一點是正確的,那麼您可使用步進來增長每一個循環迭代期間執行的工做量。經過大步前進,您能夠將原始循環的多個迭代集中到一個塊中,並按比例減小迭代次數。例如,若是您最初執行了 100次 迭代,但決定使用步長爲 4 的迭代,那麼您如今從每一個塊執行 4 次循環迭代,迭代次數爲 25次 。

自問自答

  • 一個隊列的不一樣任務能夠在多個線程執行嗎?
    答:串行隊列,異步執行時,只開一個子線程;無所謂多個線程執行;
    併發隊列,異步執行時,會自動開多個線程,能夠在多個線程併發執行不一樣的任務。

  • 一個線程能夠同時執行多個隊列的任務嗎?
    答:一個線程某個時間點只能執行一個任務,執行完畢後,可能執行到來自其餘隊列的任務(若是有的話)。好比:主線程除了執行主隊列中任務外,也可能會執行非主隊列中的任務。

    隊列與線程關係示例圖:
    queues & threads

  • qualityOfService 和 queuePriority 的區別是什麼?
    答:
    qualityOfService:
         用於表示 operation 在獲取系統資源時的優先級,默認值:NSQualityOfServiceBackground,咱們能夠根據須要給 operation 賦不一樣的優化級,如最高優化級:NSQualityOfServiceUserInteractive。
    queuePriority:
         用於設置 operation 在 operationQueue 中的相對優化級,同一 queue 中優化級高的 operation(isReady 爲 YES) 會被優先執行。
         須要注意區分 qualityOfService (在系統層面,operation 與其餘線程獲取資源的優先級) 與 queuePriority (同一 queue 中 operation 間執行的優化級)的區別。同時,須要注意 dependencies (嚴格控制執行順序)與 queuePriority (queue 內部相對優先級)的區別。

  • 添加依賴後,隊列中網絡請求任務有依賴關係時,任務結束斷定以數據返回爲準仍是以發起請求爲準?
    答:以發起請求爲準。分析過程詳見NSOperationQueue隊列中操做依賴相關思考

OperationQueue

  • NSOperation
         NSOperation 是一個"抽象類",不能直接使用。抽象類的用處是定義子類共有的屬性和方法。NSOperation 是基於 GCD 作的面向對象的封裝。相比較 GCD 使用更加簡單,而且提供了一些用 GCD 不是很好實現的功能。是蘋果公司推薦使用的併發技術。它有兩個子類:

    • NSInvocationOperation (調用操做)
    • NSBlockOperation (塊操做)
           通常經常使用NSBlockOperation,代碼簡單,同時因爲閉包性使它沒有傳參問題。任務被封裝在 NSOperation 的子類實例類對象裏,一個 NSOperation 子類對象能夠添加多個任務 block 和 一個執行完成 block ,當其關聯的全部 block 執行完時,就認爲操做結束了。
  • NSOperationQueue
          OperationQueue也是對 GCD 的高級封裝,更加面向對象,能夠實現 GCD 不方便實現的一些效果。被添加到隊列的操做默認是異步執行的。

PS:常見的抽象類有:

  • UIGestureRecognizer
  • CAAnimation
  • CAPropertyAnimation

能夠實現 非FIFO 效果

經過對不一樣操做設置依賴,或優先級,可實現 非FIFO 效果。
  代碼示例:

func testDepedence(){
        let op0 = BlockOperation.init {
            print("op0")
        }
        
        let op1 = BlockOperation.init {
            print("op1")
        }
        
        let op2 = BlockOperation.init {
            print("op2")
        }
        
        let op3 = BlockOperation.init {
            print("op3")
        }
        
        let op4 = BlockOperation.init {
            print("op4")
        }
        
        op0.addDependency(op1)
        op1.addDependency(op2)
        
        op0.queuePriority = .veryHigh
        op1.queuePriority = .normal
        op2.queuePriority = .veryLow
        
        op3.queuePriority = .low
        op4.queuePriority = .veryHigh
        
        gOpeQueue.addOperations([op0, op1, op2, op3, op4], waitUntilFinished: false)
    }

log:

op4
 op2
 op3
 op1
 op0

op4
 op3
 op2
 op1
 op0

說明:操做間不存在依賴時,按優先級執行;存在依賴時,按依賴關係前後執行(與無依賴關係的其餘任務相比,依賴集合的執行順序不肯定)

隊列暫停/繼續

經過對隊列的isSuspended屬性賦值,可實現隊列中未執行任務的暫停和繼續效果。正在執行的任務不受影響。

///暫停隊列,只對未執行中的任務有效。本例中對串行隊列的效果明顯。併發隊列因4個任務一開始就很容易一塊兒開始執行,即便掛起也沒法影響已處於執行狀態的任務。
    @IBAction func pauseQueueItemDC(_ sender: Any) {
        gOpeQueue.isSuspended = true
    }
    
    ///恢復隊列,以前未開始執行的任務會開始執行
    @IBAction func resumeQueueItemDC(_ sender: Any) {
       gOpeQueue.isSuspended = false
    }

gif 效果圖:
pauseResume

取消操做

  • 一旦添加到操做隊列中,操做對象實際上歸隊列全部,不能刪除。取消操做的惟一方法是取消它。能夠經過調用單個操做對象的 cancel 方法來取消單個操做對象,也能夠經過調用隊列對象的 cancelAllOperations 方法來取消隊列中的全部操做對象。
  • 更常見的作法是取消全部隊列操做,以響應某些重要事件,如應用程序退出或用戶專門請求取消,而不是有選擇地取消操做。

取消單個操做對象

取消(cancel)時,有 3 種狀況:
1.操做在隊列中等待執行,這種狀況下,操做將不會被執行。
2.操做已經在執行中,此時,系統不會強制中止這個操做,可是,其 cancelled屬性會被置爲 true 。
3.操做已完成,此時,cancel 無任何影響。

取消隊列中的全部操做對象

方法: cancelAllOperations。一樣只會對未執行的任務有效。
demo 中代碼:

deinit {
        gOpeQueue.cancelAllOperations()
        print("die:%@",self)
    }

自問自答

  • 經過設置操做間依賴,能夠實現 非FIFO 的指定順序效果。那麼,經過設置最大併發數爲 1 ,能夠實現指定順序效果嗎?
    A:不能夠!
    設置最大併發數爲 1 後,雖然每一個時間點只執行一個操做,可是操做的執行順序仍然基於其餘因素,如操做的依賴關係,操做的優先級(依賴關係比優先級級別更高,即先根據依賴關係排序;不存在依賴關係時,才根據優先級排序)。所以,序列化 操做隊列 不會提供與 GCD 中的序列 分派隊列 徹底相同的行爲。若是操做對象的執行順序對您很重要,那麼您應該在將操做添加到隊列以前使用 依賴關係 創建該順序,或改用 GCD 的 串行隊列 實現序列化效果。

  • Operation Queue的 block 中爲什麼無需使用 [weak self] 或 [unowned self] ?
    A:即便隊列對象是爲全局的,self -> queue -> operation block -> self,的確會形成循環引用。可是在隊列裏的操做執行完畢時,隊列會自動釋放操做,自動解除循環引用。因此沒必要使用 [weak self] 或 [unowned self] 。
    此外,這種循環引用在某些狀況下很是有用,你無需額外持有任何對象就可讓操做自動完成它的任務。好比下載頁面下載過程當中,退出有循環引用的界面時,若是不執行 cancelAllOperation 方法,能夠實現繼續執行剩餘隊列中下載任務的效果。

func addOperation(_ op: Operation)
Discussion:
Once added, the specified operation remains in the queue until it finishes executing.
Declaration

func addOperation(_ block: @escaping () -> Void)
Parameters
block
The block to execute from the operation. The block takes no parameters and has no return value.
Discussion
This method adds a single block to the receiver by first wrapping it in an operation object. You should not attempt to get a reference to the newly created operation object or determine its type information.

  • 操做的 QOS 和隊列的 QOS 有何關係?
    A:隊列的 QOS 設置,會自動把較低優先級的操做提高到與隊列相同優先級。(原更高優先級操做的優先級保持不變)。後續添加進隊列的操做,優先級低於隊列優先級時,也會被自動提高到與隊列相同的優先級。
    注意,蘋果文檔以下的解釋是錯誤的 This property specifies the service level applied to operation objects added to the queue. If the operation object has an explicit service level set, that value is used instead.
    緣由詳見:Can NSOperation have a lower qualityOfService than NSOperationQueue?

常見問題

如何解決資源競爭問題

資源競爭可能致使數據異常,死鎖,甚至因訪問野指針而崩潰。

  • 對於有明顯前後依賴關係的任務,最佳方案是 GCD串行隊列,能夠在不使用線程鎖時保證資源互斥。
  • 其餘狀況,對存在資源競爭的代碼加鎖或使用信號量(初始參數填1,表示只容許一條線程訪問資源)。
  • 串行隊列同步執行時,若是有任務相互等待,會死鎖。
    好比:在主線程上同步執行任務時,因任務和以前已加入主隊列但未執行的任務會相互等待,致使死鎖。
func testDeadLock(){
        //主隊列同步執行,會致使死鎖。block須要等待testDeadLock執行,而主隊列同步調用,又使其餘任務必須等待此block執行。因而造成了相互等待,就死鎖了。
        DispatchQueue.main.sync {
            print("main block")
        }
        print("2")
    }

可是下面代碼不會死鎖,故串行隊列同步執行任務不必定死鎖

- (void)testSynSerialQueue{
    dispatch_queue_t myCustomQueue;
    myCustomQueue = dispatch_queue_create("com.example.MyCustomQueue", NULL);
     
    dispatch_async(myCustomQueue, ^{
        printf("Do some work here.\n");
    });
     
    printf("The first block may or may not have run.\n");
     
    dispatch_sync(myCustomQueue, ^{
        printf("Do some more work here.\n");
    });
    printf("Both blocks have completed.\n");
}

如何提升代碼效率

「西餅傳說」

代碼設計優先級:系統方法 > 並行 > 串行 > 鎖,簡記爲:西餅傳說

  • 儘量依賴 系統 框架。實現併發性的最佳方法是利用系統框架提供的內置併發性。
  • 儘早識別系列任務,並儘量使它們更加 並行。若是由於某個任務依賴於某個共享資源而必須連續執行該任務,請考慮更改體系結構以刪除該共享資源。您能夠考慮爲每一個須要資源的客戶機制做資源的副本,或者徹底消除該資源。
  • 不使用鎖來保護某些共享資源,而是指定一個 串行隊列 (或使用操做對象依賴項)以正確的順序執行任務。
  • 避免使用 GCD 調度隊列操做隊列 提供的支持使得在大多數狀況下不須要鎖定。

肯定操做對象的適當範圍

  • 儘管能夠向操做隊列中添加任意大量的操做,但這樣作一般是不切實際的。與任何對象同樣,NSOperation 類的實例消耗內存,而且具備與其執行相關的實際成本。若是您的每一個操做對象只執行少許的工做,而且您建立了數以萬計的操做對象,那麼您可能會發現,您花在調度操做上的時間比花在實際工做上的時間更多。若是您的應用程序已經受到內存限制,那麼您可能會發現,僅僅在內存中擁有數萬個操做對象就可能進一步下降性能。
  • 有效使用操做的關鍵是 在你須要作的工做量和保持計算機忙碌之間找到一個適當的平衡 。儘可能確保你的業務作了合理的工做量。例如,若是您的應用程序建立了 100 個操做對象來對 100 個不一樣的值執行相同的任務,那麼能夠考慮建立 10 個操做對象來處理每一個值。
  • 您還應該避免將大量操做一次性添加到隊列中,或者避免連續地將操做對象添加到隊列中的速度快於處理它們的速度。與其用操做對象淹沒隊列,不如批量建立這些對象。當一個批處理完成執行時,使用完成塊告訴應用程序建立一個新的批處理。當您有不少工做要作時,您但願保持隊列中充知足夠的操做,以便計算機保持忙碌,可是您不但願一次建立太多操做,以致於應用程序耗盡內存。
  • 固然,您建立的操做對象的數量以及在每一個操做對象中執行的工做量是可變的,而且徹底取決於您的應用程序。你應該常用像 Instruments 這樣的工具來幫助你在效率和速度之間找到一個適當的平衡。有關 Instruments 和其餘可用於爲代碼收集度量標準的性能工具的概述,請參閱 性能概述

術語解釋摘錄

  • 異步任務(asynchronous tasks):由一個線程啓動,但實際上在另外一個線程上運行,利用額外的處理器資源更快地完成工做。
  • 互斥(mutex):提供對共享資源的互斥訪問的鎖。
    互斥鎖一次只能由一個線程持有。試圖獲取由不一樣線程持有的互斥對象會使當前線程處於休眠狀態,直到最終得到鎖爲止。
  • 進程(process):應用軟件或程序的運行時實例。
    進程有本身的虛擬內存空間和系統資源(包括端口權限) ,這些資源獨立於分配給其餘程序的資源。一個進程老是包含至少一個線程(主線程) ,而且可能包含任意數量的其餘線程。
  • 信號量(semaphore):限制對共享資源訪問的受保護變量。
    互斥(Mutexes)和條件(conditions)都是不一樣類型的信號量。
  • 任務(task),表示須要執行的工做量。
  • 線程(thread):進程中的執行流程。
    每一個線程都有本身的堆棧空間,但在其餘方面與同一進程中的其餘線程共享內存。
  • 運行循環(run loop): 一個事件處理循環,
    接收事件並派發到適當的處理程序。

官方併發編程詞彙表

本文 demo 地址

MultiThreadDemo

參考文章

Concurrency Programming Guide
iOS Concurrency: Getting Started with NSOperation and Dispatch Queues

下節預告

文中提到的知識點,「與其用操做對象淹沒隊列,不如批量建立這些對象。當一個批處理完成執行時,使用完成塊告訴應用程序建立一個新的批處理」,在最近的工做中的確有須要相似的需求,等有時間會進行總結,就做爲下一篇文章的預告吧。

本文由博客羣發一文多發等運營工具平臺 OpenWrite 發佈

相關文章
相關標籤/搜索