主頁 > 移動端開發 > iOS多執行緒之超實用理論+demo演示(可下載)

iOS多執行緒之超實用理論+demo演示(可下載)

2020-09-22 19:24:38 移動端開發

目錄
  • 背景簡介
  • GCD、OperationQueue 對比
    • 核心理念
    • 區別
  • GCD
    • 佇列
      • 串行佇列(Serial Queues)
      • 并發佇列(Concurrent Queues)
      • 串行、并發佇列對比圖
      • 注意事項
    • block(塊)相關
    • dispatch_after
    • dispatch_semaphore
    • dispatch_apply
    • 自問自答
  • OperationQueue
    • 可以實作 非FIFO 效果
    • 佇列暫停/繼續
    • 取消操作
      • 取消單個操作物件
      • 取消佇列中的所有操作物件
    • 自問自答
  • 常見問題
    • 如何解決資源競爭問題
    • 如何提高代碼效率
      • “西餅傳說”
      • 確定操作物件的適當范圍
  • 術語解釋摘錄
  • 本文 demo 地址
  • 參考文章
  • 下節預告

背景簡介

     在初學iOS相關知識程序中,大多都對多執行緒有些恐懼的心里,同時感覺作業中用上的概率不大,但是如果平時不多積累并學透多執行緒,當作業中真的需要用到的時候,就很可能簡單百度后把一些知識點稀里糊涂地就用到作業中了,殊不知里面有很多的坑,也有很多技巧需要在理論上先做了解,再結合實戰,進一步去體會多執行緒的魅力和強大,

     接下來,就對多執行緒來源的背景進行簡單的介紹:

     在計算的早期,計算機可以執行的最大作業量是由 CPU 的時鐘速度決定的,但是隨著技術的進步和處理器設計的緊炊訓,熱量和其他物理約束開始限制處理器的最大時鐘速度,因此,芯片制造商尋找其他方法來提高芯片的總體性能,他們決定的解決方案是增加每個芯片上的處理器核心數量,通過增加內核的數量,一個單獨的芯片可以每秒執行更多的指令,而不用增加 CPU 的速度或改變芯片的大小或熱特性,唯一的問題是如何利用額外的內核,

     應用程式使用多核的傳統方法是創建多個執行緒,與依賴執行緒不同,iOS 采用異步設計方法來解決并發問題,通常,這項作業涉及獲取一個后臺執行緒,在該執行緒上啟動所需的任務,然后在任務完成時向呼叫方發送通知(通常通過一個回呼函式),

     iOS 提供了一些技術,允許您異步執行任何任務,而無需自己管理執行緒,異步啟動任務的技術之一是 Grand Central Dispatch (GCD),這種技術采用執行緒管理代碼,并將該代碼移動到系統級別,您所要做的就是定義要執行的任務,并將它們添加到適當的分派佇列中,GCD 負責創建所需的執行緒,并安排任務在這些執行緒上運行,由于執行緒管理現在是系統的一部分,GCD 提供了任務管理和執行的整體方法,比傳統執行緒提供了更高的效率,

     OperationQueue(操作佇列,api 類名為 NSOperationQueue )是 Objective-C 物件,是對 GCD 的封裝,其作用非常類似于分派佇列,您定義要執行的任務,然后將它們添加到 OperationQueue 中, OperationQueue 處理這些任務的調度和執行,與 GCD 一樣, OperationQueue 為您處理所有執行緒管理,確保在系統上盡可能快速有效地執行任務,

     接下來,就對現在作業中常用的這兩種技術進行比較和實體決議,

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 發布

轉載請註明出處,本文鏈接:https://www.uj5u.com/yidong/106828.html

標籤:其他

上一篇:知識體系索引

下一篇:Android連載34-更新資料庫以及使用SDK自帶adb工具

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • 【從零開始擼一個App】Dagger2

    Dagger2是一個IOC框架,一般用于Android平臺,第一次接觸的朋友,一定會被搞得暈頭轉向。它延續了Java平臺Spring框架代碼碎片化,注解滿天飛的傳統。嘗試將各處代碼片段串聯起來,理清思緒,真不是件容易的事。更不用說還有各版本細微的差別。 與Spring不同的是,Spring是通過反射 ......

    uj5u.com 2020-09-10 06:57:59 more
  • Flutter Weekly Issue 66

    新聞 Flutter 季度調研結果分享 教程 Flutter+FaaS一體化任務編排的思考與設計 詳解Dart中如何通過注解生成代碼 GitHub 用對了嗎?Flutter 團隊分享如何管理大型開源專案 插件 flutter-bubble-tab-indicator A Flutter librar ......

    uj5u.com 2020-09-10 06:58:52 more
  • Proguard 常用規則

    介紹 Proguard 入口,如何查看輸出,如何使用 keep 設定入口以及使用實體,如何配置壓縮,混淆,校驗等規則。

    ......

    uj5u.com 2020-09-10 06:59:00 more
  • Android 開發技術周報 Issue#292

    新聞 Android即將獲得類AirDrop功能:可向附近設備快速分享檔案 谷歌為安卓檔案管理應用引入可安全隱藏資料的Safe Folder功能 Android TV新主界面將顯示電影、電視節目和應用推薦內容 泄露的Android檔案暗示了傳說中的谷歌Pixel 5a與折疊屏新機 谷歌發布Andro ......

    uj5u.com 2020-09-10 07:00:37 more
  • AutoFitTextureView Error inflating class

    報錯: Binary XML file line #0: Binary XML file line #0: Error inflating class xxx.AutoFitTextureView 解決: <com.example.testy2.AutoFitTextureView android: ......

    uj5u.com 2020-09-10 07:00:41 more
  • 根據Uri,Cursor沒有獲取到對應的屬性

    Android: 背景:呼叫攝像頭,拍攝視頻,指定保存的地址,但是回傳的Cursor檔案,只有名稱和大小的屬性,沒有其他諸如時長,連ID屬性都沒有 使用 cursor.getInt(cursor.getColumnIndexOrThrow(MediaStore.Video.Media.DURATIO ......

    uj5u.com 2020-09-10 07:00:44 more
  • Android連載29-持久化技術

    一、持久化技術 我們平時所使用的APP產生的資料,在記憶體中都是瞬時的,會隨著斷電、關機等丟失資料,因此android系統采用了持久化技術,用于存盤這些“瞬時”資料 持久化技術包括:檔案存盤、SharedPreference存盤以及資料庫存盤,還有更復雜的SD卡記憶體儲。 二、檔案存盤 最基本存盤方式, ......

    uj5u.com 2020-09-10 07:00:47 more
  • Android Camera2Video整合到自己專案里

    背景: Android專案里呼叫攝像頭拍攝視頻,原本使用的 MediaStore.ACTION_VIDEO_CAPTURE, 后來因專案需要,改成了camera2 1.Camera2Video 官方demo有點問題,下載后,不能直接整合到專案 問題1.多次拍攝視頻崩潰 問題2.雙擊record按鈕, ......

    uj5u.com 2020-09-10 07:00:50 more
  • Android 開發技術周報 Issue#293

    新聞 谷歌為Android TV開發者提供多種新功能 Android 11將自動填表功能整合到鍵盤輸入建議中 谷歌宣布Android Auto即將支持更多的導航和數字停車應用 谷歌Pixel 5只有XL版本 搭載驍龍765G且將比Pixel 4更便宜 [圖]Wear OS將迎來重磅更新:應用啟動時間 ......

    uj5u.com 2020-09-10 07:01:38 more
  • 海豚星空掃碼投屏 Android 接收端 SDK 集成 六步驟

    掃碼投屏,開放網路,獨占設備,不需要額外下載軟體,微信掃碼,發現設備。支持標準DLNA協議,支持倍速播放。視頻,音頻,圖片投屏。好點意思。還支持自定義基于 DLNA 擴展的操作動作。好像要收費,沒體驗。 這里簡單記錄一下集成程序。 一 跟目錄的build.gradle添加私有mevan倉庫 mave ......

    uj5u.com 2020-09-10 07:01:43 more
最新发布
  • 歡迎頁輪播影片

    如圖,引導開始,球從上落下,同時淡入文字,然后文字開始輪播,最后一頁時停止,點擊進入首頁。 在來看看效果圖。 重力球先不講,主要歡迎輪播簡單實作 首先新建一個類 TextTranslationXGuideView,用于影片展示 文本是類似的,最后會有個圖片箭頭影片,布局很簡單,就是一個 TextVi ......

    uj5u.com 2023-04-20 08:40:31 more
  • 【FAQ】關于華為推送服務因營銷訊息頻次管控導致服務通訊類訊息

    一. 問題描述 使用華為推送服務下發IM訊息時,下發訊息請求成功且code碼為80000000,但是手機總是收不到訊息; 在華為推送自助分析(Beta)平臺查看發現,訊息發送觸發了頻控。 二. 問題原因及背景 2023年1月05日起,華為推送服務對咨詢營銷類訊息做了單個設備每日推送數量上限管理,具體 ......

    uj5u.com 2023-04-20 08:40:11 more
  • 歡迎頁輪播影片

    如圖,引導開始,球從上落下,同時淡入文字,然后文字開始輪播,最后一頁時停止,點擊進入首頁。 在來看看效果圖。 重力球先不講,主要歡迎輪播簡單實作 首先新建一個類 TextTranslationXGuideView,用于影片展示 文本是類似的,最后會有個圖片箭頭影片,布局很簡單,就是一個 TextVi ......

    uj5u.com 2023-04-20 08:39:36 more
  • 【FAQ】關于華為推送服務因營銷訊息頻次管控導致服務通訊類訊息

    一. 問題描述 使用華為推送服務下發IM訊息時,下發訊息請求成功且code碼為80000000,但是手機總是收不到訊息; 在華為推送自助分析(Beta)平臺查看發現,訊息發送觸發了頻控。 二. 問題原因及背景 2023年1月05日起,華為推送服務對咨詢營銷類訊息做了單個設備每日推送數量上限管理,具體 ......

    uj5u.com 2023-04-20 08:39:13 more
  • iOS從UI記憶體地址到讀取成員變數(oc/swift)

    開發除錯時,我們發現bug時常首先是從UI顯示發現例外,下一步才會去定位UI相關連的資料的。XCode有給我們提供一系列debug工具,但是很多人可能還沒有形成一套穩定的除錯流程,因此本文嘗試解決這個問題,順便提出一個暴論:UI顯示例外問題只需要兩個步驟就能完成定位作業的80%: 定位例外 UI 組 ......

    uj5u.com 2023-04-19 09:16:23 more
  • FIDE重磅更新!性能飛躍!體驗有禮!

    FIDE 開發者工具重構升級啦!實作500%性能提升,誠邀體驗! 一直以來不少開發者朋友在社區反饋,在使用 FIDE 工具的程序中,時常會遇到諸如加載不及時、代碼預覽/渲染性能不如意的情況,十分影響開發體驗。 作為技術團隊,我們深知一件趁手的開發工具對開發者的重要性,因此,在2023年開年,FinC ......

    uj5u.com 2023-04-19 09:16:15 more
  • 游戲內嵌社區服務開放,助力開發者提升玩家互動與留存

    華為 HMS Core 游戲內嵌社區服務提供快速訪問華為游戲中心論壇能力,支持玩家直接在游戲內瀏覽帖子和交流互動,助力開發者擴展內容生產和觸達的場景。 一、為什么要游戲內嵌社區? 二、游戲內嵌社區的典型使用場景 1、游戲內打開論壇 您可以在游戲內繪制論壇入口,為玩家提供沉浸式發帖、瀏覽、點贊、回帖、 ......

    uj5u.com 2023-04-19 09:15:46 more
  • iOS從UI記憶體地址到讀取成員變數(oc/swift)

    開發除錯時,我們發現bug時常首先是從UI顯示發現例外,下一步才會去定位UI相關連的資料的。XCode有給我們提供一系列debug工具,但是很多人可能還沒有形成一套穩定的除錯流程,因此本文嘗試解決這個問題,順便提出一個暴論:UI顯示例外問題只需要兩個步驟就能完成定位作業的80%: 定位例外 UI 組 ......

    uj5u.com 2023-04-19 09:14:53 more
  • FIDE重磅更新!性能飛躍!體驗有禮!

    FIDE 開發者工具重構升級啦!實作500%性能提升,誠邀體驗! 一直以來不少開發者朋友在社區反饋,在使用 FIDE 工具的程序中,時常會遇到諸如加載不及時、代碼預覽/渲染性能不如意的情況,十分影響開發體驗。 作為技術團隊,我們深知一件趁手的開發工具對開發者的重要性,因此,在2023年開年,FinC ......

    uj5u.com 2023-04-19 09:14:08 more
  • 游戲內嵌社區服務開放,助力開發者提升玩家互動與留存

    華為 HMS Core 游戲內嵌社區服務提供快速訪問華為游戲中心論壇能力,支持玩家直接在游戲內瀏覽帖子和交流互動,助力開發者擴展內容生產和觸達的場景。 一、為什么要游戲內嵌社區? 二、游戲內嵌社區的典型使用場景 1、游戲內打開論壇 您可以在游戲內繪制論壇入口,為玩家提供沉浸式發帖、瀏覽、點贊、回帖、 ......

    uj5u.com 2023-04-19 09:08:34 more