iOS多線程之GCD劲件、OperationQueue 探索開括

收錄:www.cocoachina.com/articles/90…

簡介

iOS 提供了一些技術(shù),允許您異步執(zhí)行任何任務谷浅,而無需自己管理線程秘血。異步啟動任務的技術(shù)之一是 Grand Central Dispatch (GCD)。
這種技術(shù)采用線程管理代碼紧唱,并將該代碼移動到系統(tǒng)級別活尊。
您所要做的就是定義要執(zhí)行的任務,并將它們添加到適當?shù)姆峙申犃兄小?br> GCD 負責創(chuàng)建所需的線程漏益,并安排任務在這些線程上運行酬凳。由于線程管理現(xiàn)在是系統(tǒng)的一部分,GCD 提供了任務管理和執(zhí)行的整體方法遭庶,比傳統(tǒng)線程提供了更高的效率宁仔。

OperationQueue(操作隊列,api 類名為 NSOperationQueue )是 Objective-C 對象峦睡,是對 GCD 的封裝翎苫。其作用非常類似于分派隊列。
您定義要執(zhí)行的任務榨了,然后將它們添加到 OperationQueue 中煎谍, OperationQueue 處理這些任務的調(diào)度和執(zhí)行。
與 GCD 一樣龙屉, OperationQueue 為您處理所有線程管理呐粘,確保在系統(tǒng)上盡可能快速有效地執(zhí)行任務满俗。

GCD、OperationQueue 對比

核心理念

  • GCD的核心概念:
    • 將 任務(block) 添加到隊列作岖,并且指定執(zhí)行任務的函數(shù)唆垃。
  • NSOperation 的核心概念:
    • 把 操作(異步) 添加到 隊列。

區(qū)別

  • GCD:

    • 將任務(block)添加到隊列(串行/并發(fā)/主隊列)痘儡,并且指定任務執(zhí)行的函數(shù)(同步/異步)
    • GCD是底層的C語言構(gòu)成的API
    • iOS 4.0 推出的辕万,針對多核處理器的并發(fā)技術(shù)
    • 在隊列中執(zhí)行的是由 block 構(gòu)成的任務,這是一個輕量級的數(shù)據(jù)結(jié)構(gòu)
    • 要停止已經(jīng)加入 queue 的 block 需要寫復雜的代碼
    • 需要通過 Barrier(dispatch_barrier_async)或者同步任務設置任務之間的依賴關(guān)系
    • 只能設置隊列的優(yōu)先級
    • 高級功能:
      • dispatch_once_t(一次性執(zhí)行, 多線程安全)沉删;
      • dispatch_after(延遲)渐尿; dispatch_group(調(diào)度組); dispatch_semaphore(信號量)矾瑰;
      • dispatch_apply(優(yōu)化順序不敏感大體量for循環(huán))砖茸;
  • OperationQueue:

    • OC 框架,更加面向?qū)ο笈寡ǎ菍?GCD 的封裝渔彰。

    • iOS 2.0 推出的,蘋果推出 GCD 之后推正,對 NSOperation 的底層進行了全部重寫恍涂。

    • 可以設置隊列中每一個操作的 QOS() 隊列的整體 QOS

    • 操作相關(guān) Operation作為一個對象,為我們提供了更多的選擇:

      • 任務依賴(addDependency)植榕,可以跨隊列設置操作的依賴關(guān)系再沧;
      • 在隊列中的優(yōu)先級(queuePriority) 服務質(zhì)量(qualityOfService, iOS8+);
      • 完成回調(diào)(void (^completionBlock)(void)
    • 隊列相關(guān) 服務質(zhì)量(qualityOfService, iOS8+);

    • 最大并發(fā)操作數(shù)(maxConcurrentOperationCount),GCD 不易實現(xiàn); 暫停/繼續(xù)(suspended)尊残;

    • 取消所有操作(cancelAllOperations)炒瘸;

    • KVO 監(jiān)聽隊列任務執(zhí)行進度(progress, iOS13+);

GCD

從GCD常見面試題到底層源碼分析寝衫,帶你深入了解不一樣的GCD
1顷扩、GCD常見面試題分析
2、GCD的注意事項(死鎖)
3慰毅、GCD的底層原理(隊列分析)

觀看點擊▼

iOS多線程-『GCD』

隊列

串行隊列(Serial Queues)

串行隊列中的任務按順序執(zhí)行隘截;
但是不同串行隊列間沒有任何約束;
多個串行隊列同時執(zhí)行時汹胃,不同隊列中任務執(zhí)行是并發(fā)的效果婶芭。

比如:火車站買票可以有多個賣票口,但是每個排的隊都是串行隊列着饥,整體并發(fā)犀农,單線串行。

注意防坑:串行隊列創(chuàng)建的位置宰掉。

比如下面代碼示例中:

  • 在for循環(huán)內(nèi)部創(chuàng)建時呵哨,每個循環(huán)都是創(chuàng)建一個新的串行隊列赁濒,里面只裝一個任務,多個串行隊列孟害,結(jié)果整體上是并發(fā)的效果拒炎。
    想要串行效果,必須在for循環(huán)外部創(chuàng)建串行隊列纹坐。

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

代碼示例:

    private func serialExcuteByGCD(){
        let lArr : [UIImageView] = [imageView1, imageView2, imageView3, imageView4]

        //串行隊列,異步執(zhí)行時球切,只開一個子線程
        let serialQ = DispatchQueue.init(label: "com.companyName.serial.downImage")

        for i in 0..<lArr.count{
            let lImgV = lArr[i]

            //清空舊圖片
            lImgV.image = nil

         //注意谷誓,防坑:串行隊列創(chuàng)建的位置,在這創(chuàng)建時,每個循環(huán)都是一個新的串行隊列吨凑,里面只裝一個任務捍歪,多個串行隊列,整體上是并行的效果鸵钝。
            //            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)個 結(jié)束")
                    DispatchQueue.main.async {
                        print("第\(i)個 切到主線程更新圖片")
                        lImgV.image = img
                    }
                    if nil == img{
                        print("第\(i+1)個img is nil")
                    }
                }
            }
        }
    }

并發(fā)隊列(Concurrent Queues)

并發(fā)隊列依舊保證中任務按加入的先后順序開始(FIFO),但是無法知道執(zhí)行順序恩商,執(zhí)行時長和某一時刻的任務數(shù)变逃。按 FIFO 開始后,他們之間不會相互等待怠堪。

比如:提交了 #1揽乱,#2,#3 任務到并發(fā)隊列粟矿,開始的順序是 #1凰棉,#2,#3陌粹。#2 和 #3 雖然開始的比 #1 晚撒犀,但是可能比 #1 執(zhí)行結(jié)束的還要早。任務的執(zhí)行是由系統(tǒng)決定的掏秩,所以執(zhí)行時長和結(jié)束時間都無法確定绘证。

需要用到并發(fā)隊列時,強烈建議 使用系統(tǒng)自帶的四種全局隊列之一哗讥。但是嚷那,當你需要使用 barrier 對隊列中任務進行柵欄時,只能使用自定義并發(fā)隊列杆煞。

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 和鎖的區(qū)別

  • 依賴對象不同魏宽,barrier 依賴的對象是自定義并發(fā)隊列腐泻,鎖操作依賴的對象是線程。
  • 作用不同队询,barrier 起到自定義并發(fā)隊列中柵欄的作用派桩;鎖起到多線程操作時防止資源競爭的作用。

代碼示例:

private func concurrentExcuteByGCD(){
        let lArr : [UIImageView] = [imageView1, imageView2, imageView3, imageView4]

        for i in 0..<lArr.count{
            let lImgV = lArr[i]

            //清空舊圖片
            lImgV.image = nil

            //并行隊列:圖片下載任務按順序開始蚌斩,但是是并行執(zhí)行铆惑,不會相互等待,任務結(jié)束和圖片顯示順序是無序的送膳,多個子線程同時執(zhí)行员魏,性能更佳。
            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)個結(jié)束")
                    DispatchQueue.main.async {
                        lImgV.image = img
                    }
                    if nil == img{
                        print("第\(i+1)個img is nil")
                    }
                }
            }
        }
    }

串行撕阎、并發(fā)隊列對比圖

注意事項

  • 無論串行還是并發(fā)隊列,都是 FIFO 碌补;
    一般創(chuàng)建 任務(blocks)和加任務到隊列是在主線程虏束,但是任務執(zhí)行一般是在其他線程(asyc)。
    需要刷新 UI 時厦章,如果當前不再主線程镇匀,需要切回主線程執(zhí)行。
    當不確定當前線程是否在主線程時袜啃,可以使用下面代碼:

    /**
    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);
    }
    }
    
  • 主隊列是串行隊列汗侵,每個時間點只能有一個任務執(zhí)行,因此如果耗時操作放到主隊列囊骤,會導致界面卡頓晃择。

  • 系統(tǒng)提供一個串行主隊列,<u style="box-sizing: border-box;">4個</u> 不同優(yōu)先級的全局隊列也物。 用 dispatch_get_global_queue 方法獲取全局隊列時宫屠,第一個參數(shù)有 4 種類型可選:

    • DISPATCH_QUEUE_PRIORITY_HIGH
    • DISPATCH_QUEUE_PRIORITY_DEFAULT
    • DISPATCH_QUEUE_PRIORITY_LOW
    • DISPATCH_QUEUE_PRIORITY_BACKGROUND
  • 串行隊列異步執(zhí)行時,切到主線程刷 UI 也需要時間滑蚯,切換完成之前浪蹂,指令可能已經(jīng)執(zhí)行到下個循環(huán)了。但是看起來圖片還是依次下載完成和顯示的告材,因為每一張圖切到主線程顯示都需要時間坤次。詳見 demo 示例。

  • iOS8 之后斥赋,如果需要添加可被取消的任務缰猴,可以使用 DispatchWorkItem 類,此類有 cancel 方法疤剑。

  • 應該避免創(chuàng)建大量的串行隊列,如果希望并發(fā)執(zhí)行大量任務滑绒,請將它們提交給全局并發(fā)隊列之一闷堡。創(chuàng)建串行隊列時,請嘗試為每個隊列確定一個用途疑故,例如保護資源或同步應用程序的某些關(guān)鍵行為(如藍牙檢測結(jié)果需要有序處理的邏輯)杠览。

block(塊)相關(guān)

堆棧到底有什么區(qū)別?為什么面試中經(jīng)常問到纵势?

  • 堆的Block能捕獲變量踱阿,棧的Block是否也行?
  • 棧的Block堆上的使用有什么區(qū)別钦铁?
  • __weak真的能解決循環(huán)引用嗎软舌?
  • 如何理解Block底層的結(jié)構(gòu)體?

觀看點擊▼

Block底層原理與LLDB Plugin

調(diào)度隊列復制添加到它們中的塊育瓜,并在執(zhí)行完成時釋放塊葫隙。
雖然隊列在執(zhí)行小任務時比原始線程更有效栽烂,但是創(chuàng)建塊并在隊列上執(zhí)行它們?nèi)匀淮嬖陂_銷躏仇。
如果一個塊執(zhí)行的工作量太少,那么內(nèi)聯(lián)執(zhí)行它可能比將它分派到隊列中要便宜得多腺办。
判斷一個塊是否工作量太少的方法是使用性能工具為每個路徑收集度量數(shù)據(jù)并進行比較焰手。
您可能希望將 block 的部分代碼包含在 @autoreleasepool 中,以處理這些對象的內(nèi)存管理怀喉。
盡管 GCD 調(diào)度隊列擁有自己的自動釋放池书妻,但它們不能保證這些池何時耗盡。
如果您的應用程序是內(nèi)存受限的躬拢,那么創(chuàng)建您自己的自動釋放池可以讓您以更有規(guī)律的間隔釋放自動釋放對象的內(nèi)存躲履。

dispatch_after

dispatch_after 函數(shù)并不是在指定時間之后才開始執(zhí)行處理,而是在指定時間之后將任務追加到隊列中聊闯。
這個時間并不是絕對準確的工猜。

代碼示例:

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

dispatch_semaphore

在多線程訪問可變變量時,是非線程安全的菱蔬。
可能導致程序崩潰篷帅。
此時,可以通過使用信號量(semaphore)技術(shù)拴泌,保證多線程處理某段代碼時魏身,后面線程等待前面線程執(zhí)行,保證了多線程的安全性蚪腐。
使用方法記兩個就行了箭昵,一個是wait(dispatch_semaphore_wait),一個是signal(dispatch_semaphore_signal)回季。

dispatch_apply

當每次迭代中執(zhí)行工作與其他所有迭代中執(zhí)行的工作不同家制,且每個循環(huán)完成的順序不重要時掉房,可以用 dispatch_apply 函數(shù)替換循環(huán)。

注意:替換后慰丛, dispatch_apply 函數(shù)整體上是同步執(zhí)行卓囚,內(nèi)部 block 的執(zhí)行類型(串行/并發(fā))由隊列類型決定,但是串行隊列易死鎖诅病,建議用并發(fā)隊列哪亿。

原循環(huán):

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

優(yōu)化后:

dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

 //count 是迭代的總次數(shù)。
dispatch_apply(count, queue, ^(size_t i) {
   printf("%u\n",i);
});

//同樣在上面循環(huán)結(jié)束后才調(diào)用贤笆。
printf("done");

您應該確保您的任務代碼在每次迭代中完成合理數(shù)量的工作蝇棉。
與您分派到隊列的任何塊或函數(shù)一樣,調(diào)度該代碼以便執(zhí)行會帶來開銷芥永。
如果循環(huán)的每次迭代只執(zhí)行少量的工作篡殷,那么調(diào)度代碼的開銷可能會超過將代碼分派到隊列可能帶來的性能優(yōu)勢。
如果您在測試期間發(fā)現(xiàn)這一點是正確的埋涧,那么您可以使用步進來增加每個循環(huán)迭代期間執(zhí)行的工作量板辽。
通過大步前進,您可以將原始循環(huán)的多個迭代集中到一個塊中棘催,并按比例減少迭代次數(shù)劲弦。
例如,如果您最初執(zhí)行了 100次 迭代醇坝,但決定使用步長為 4 的迭代邑跪,那么您現(xiàn)在從每個塊執(zhí)行 4 次循環(huán)迭代,迭代次數(shù)為 25次 呼猪。

自問自答

  • 一個隊列的不同任務可以在多個線程執(zhí)行嗎画畅?

    • 串行隊列,異步執(zhí)行時宋距,只開一個子線程轴踱;
    • 無所謂多個線程執(zhí)行;
    • 并發(fā)隊列乡革,異步執(zhí)行時寇僧,會自動開多個線程,可以在多個線程并發(fā)執(zhí)行不同的任務沸版。
  • 一個線程可以同時執(zhí)行多個隊列的任務嗎嘁傀?

    • 一個線程某個時間點只能執(zhí)行一個任務,執(zhí)行完畢后视粮,可能執(zhí)行到來自其他隊列的任務(如果有的話)细办。
      比如:主線程除了執(zhí)行主隊列中任務外,也可能會執(zhí)行非主隊列中的任務。

隊列與線程關(guān)系示例圖:
  • qualityOfService 和 queuePriority 的區(qū)別是什么笑撞?

    • qualityOfService:
      • 用于表示 operation 在獲取系統(tǒng)資源時的優(yōu)先級岛啸,默認值:NSQualityOfServiceBackground,我們可以根據(jù)需要給 operation 賦不同的優(yōu)化級茴肥,如最高優(yōu)化級:NSQualityOfServiceUserInteractive坚踩。
    • queuePriority:
      • 用于設置 operation 在 operationQueue 中的相對優(yōu)化級,同一 queue 中優(yōu)化級高的 operation(isReady 為 YES) 會被優(yōu)先執(zhí)行瓤狐。
        需要注意區(qū)分 qualityOfService (在系統(tǒng)層面瞬铸,operation 與其他線程獲取資源的優(yōu)先級) 與 queuePriority (同一 queue 中 operation 間執(zhí)行的優(yōu)化級)的區(qū)別。
        同時础锐,需要注意 dependencies (嚴格控制執(zhí)行順序)與 queuePriority (queue 內(nèi)部相對優(yōu)先級)的區(qū)別嗓节。
  • 添加依賴后,隊列中網(wǎng)絡請求任務有依賴關(guān)系時皆警,任務結(jié)束判定以數(shù)據(jù)返回為準還是以發(fā)起請求為準拦宣?

    • 以發(fā)起請求為準。

OperationQueue

NSOperation NSOperation 是一個"抽象類"信姓,不能直接使用鸵隧。
抽象類的用處是定義子類共有的屬性和方法。
NSOperation 是基于 GCD 做的面向?qū)ο蟮姆庋b财破。
相比較 GCD 使用更加簡單掰派,并且提供了一些用 GCD 不是很好實現(xiàn)的功能从诲。
是蘋果公司推薦使用的并發(fā)技術(shù)左痢。

它有兩個子類:

  • NSInvocationOperation (調(diào)用操作)
  • NSBlockOperation (塊操作) 一般常用NSBlockOperation,代碼簡單系洛,同時由于閉包性使它沒有傳參問題俊性。任務被封裝在 NSOperation 的子類實例類對象里,一個 NSOperation 子類對象可以添加多個任務 block 和 一個執(zhí)行完成 block 描扯,當其關(guān)聯(lián)的所有 block 執(zhí)行完時定页,就認為操作結(jié)束了。
  • NSOperationQueue OperationQueue也是對 GCD 的高級封裝绽诚,更加面向?qū)ο蟮浠玻梢詫崿F(xiàn) GCD 不方便實現(xiàn)的一些效果。被添加到隊列的操作默認是異步執(zhí)行的恩够。

PS:常見的抽象類有:

  • UIGestureRecognizer

  • CAAnimation

  • CAPropertyAnimation

    可以實現(xiàn) 非FIFO 效果

通過對不同操作設置依賴,或優(yōu)先級卒落,可實現(xiàn) 非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)
    }

說明:操作間不存在依賴時蜂桶,按優(yōu)先級執(zhí)行儡毕;存在依賴時,按依賴關(guān)系先后執(zhí)行(與無依賴關(guān)系的其他任務相比扑媚,依賴集合的執(zhí)行順序不確定)

隊列暫停/繼續(xù)

通過對隊列的isSuspended屬性賦值腰湾,可實現(xiàn)隊列中未執(zhí)行任務的暫停和繼續(xù)效果雷恃。正在執(zhí)行的任務不受影響。

///暫停隊列,只對未執(zhí)行中的任務有效狸捕。本例中對串行隊列的效果明顯锈至。并發(fā)隊列因4個任務一開始就很容易一起開始執(zhí)行,即使掛起也無法影響已處于執(zhí)行狀態(tài)的任務导犹。
    @IBAction func pauseQueueItemDC(_ sender: Any) {
        gOpeQueue.isSuspended = true
    }

    ///恢復隊列,之前未開始執(zhí)行的任務會開始執(zhí)行
    @IBAction func resumeQueueItemDC(_ sender: Any) {
       gOpeQueue.isSuspended = false
    }

取消操作

  • 一旦添加到操作隊列中羡忘,操作對象實際上歸隊列所有谎痢,不能刪除。取消操作的唯一方法是取消它卷雕〗谠常可以通過調(diào)用單個操作對象的 cancel 方法來取消單個操作對象,也可以通過調(diào)用隊列對象的 cancelAllOperations 方法來取消隊列中的所有操作對象漫雕。
  • 更常見的做法是取消所有隊列操作滨嘱,以響應某些重要事件,如應用程序退出或用戶專門請求取消浸间,而不是有選擇地取消操作太雨。

取消單個操作對象

取消(cancel)時,有 3 種情況:

1.操作在隊列中等待執(zhí)行魁蒜,這種情況下囊扳,操作將不會被執(zhí)行。
2.操作已經(jīng)在執(zhí)行中兜看,此時锥咸,系統(tǒng)不會強制停止這個操作,但是细移,其 cancelled屬性會被置為 true 搏予。
3.操作已完成,此時弧轧,cancel 無任何影響雪侥。

取消隊列中的所有操作對象

方法: cancelAllOperations。同樣只會對未執(zhí)行的任務有效精绎。

demo 中代碼:

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

自問自答

  • 通過設置操作間依賴速缨,可以實現(xiàn) 非FIFO 的指定順序效果。那么捺典,通過設置最大并發(fā)數(shù)為 1 鸟廓,可以實現(xiàn)指定順序效果嗎?

不可以! 設置最大并發(fā)數(shù)為 1 后引谜,雖然每個時間點只執(zhí)行一個操作牍陌,但是操作的執(zhí)行順序仍然基于其他因素,如操作的依賴關(guān)系员咽,操作的優(yōu)先級(依賴關(guān)系比優(yōu)先級級別更高毒涧,即先根據(jù)依賴關(guān)系排序;
不存在依賴關(guān)系時,才根據(jù)優(yōu)先級排序)贝室。
因此契讲,序列化 操作隊列 不會提供與 GCD 中的序列 分派隊列 完全相同的行為。
如果操作對象的執(zhí)行順序?qū)δ苤匾担敲茨鷳撛趯⒉僮魈砑拥疥犃兄笆褂?依賴關(guān)系 建立該順序捡偏,或改用 GCD 的 串行隊列 實現(xiàn)序列化效果。

  • Operation Queue的 block 中為何無需使用 [weak self] 或 [unowned self] 峡迷?

即使隊列對象是為全局的银伟,self -> queue -> operation block -> self,的確會造成循環(huán)引用绘搞。
但是在隊列里的操作執(zhí)行完畢時彤避,隊列會自動釋放操作,自動解除循環(huán)引用夯辖。所以不必使用 [weak self] 或 [unowned self] 琉预。
此外,這種循環(huán)引用在某些情況下非常有用蒿褂,你無需額外持有任何對象就可以讓操作自動完成它的任務圆米。
比如下載頁面下載過程中,退出有循環(huán)引用的界面時贮缅,如果不執(zhí)行 cancelAllOperation 方法榨咐,可以實現(xiàn)繼續(xù)執(zhí)行剩余隊列中下載任務的效果。

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 有何關(guān)系谴供?

隊列的 QOS 設置,會自動把較低優(yōu)先級的操作提升到與隊列相同優(yōu)先級齿坷。(原更高優(yōu)先級操作的優(yōu)先級保持不變)桂肌。
后續(xù)添加進隊列的操作,優(yōu)先級低于隊列優(yōu)先級時永淌,也會被自動提升到與隊列相同的優(yōu)先級崎场。
注意,蘋果文檔如下的解釋是錯誤的 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.

常見問題

如何解決資源競爭問題

資源競爭可能導致數(shù)據(jù)異常遂蛀,死鎖谭跨,甚至因訪問野指針而崩潰。

  • 對于有明顯先后依賴關(guān)系的任務,最佳方案是 GCD串行隊列,可以在不使用線程鎖時保證資源互斥螃宙。

  • 其他情況蛮瞄,對存在資源競爭的代碼加鎖或使用信號量(初始參數(shù)填1,表示只允許一條線程訪問資源)谆扎。

  • 串行隊列同步執(zhí)行時挂捅,如果有任務相互等待,會死鎖堂湖。
    比如:在主線程上同步執(zhí)行任務時闲先,因任務和之前已加入主隊列但未執(zhí)行的任務會相互等待,導致死鎖无蜂。

    func testDeadLock(){
        //主隊列同步執(zhí)行伺糠,會導致死鎖。block需要等待testDeadLock執(zhí)行斥季,而主隊列同步調(diào)用退盯,又使其他任務必須等待此block執(zhí)行。于是形成了相互等待泻肯,就死鎖了渊迁。
        DispatchQueue.main.sync {
            print("main block")
        }
        print("2")
    }
    

但是下面代碼不會死鎖,故串行隊列同步執(zhí)行任務不一定死鎖灶挟。

- (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");
}

如何提高代碼效率

“西餅傳說”

代碼設計優(yōu)先級:
系統(tǒng)方法 > 并行 > 串行 > 鎖琉朽,簡記為:<u style="box-sizing: border-box;">西餅傳說</u>

  • 盡可能依賴 系統(tǒng) 框架。實現(xiàn)并發(fā)性的最佳方法是利用系統(tǒng)框架提供的內(nèi)置并發(fā)性稚铣。
  • 盡早識別系列任務箱叁,并盡可能使它們更加 并行。如果因為某個任務依賴于某個共享資源而必須連續(xù)執(zhí)行該任務惕医,請考慮更改體系結(jié)構(gòu)以刪除該共享資源耕漱。您可以考慮為每個需要資源的客戶機制作資源的副本,或者完全消除該資源抬伺。
  • 不使用鎖來保護某些共享資源螟够,而是指定一個 串行隊列 (或使用操作對象依賴項)以正確的順序執(zhí)行任務。
  • 避免使用 峡钓。GCD 調(diào)度隊列操作隊列 提供的支持使得在大多數(shù)情況下不需要鎖定妓笙。

術(shù)語解釋摘錄

  • 異步任務(asynchronous tasks):
    • 由一個線程啟動,但實際上在另一個線程上運行能岩,利用額外的處理器資源更快地完成工作寞宫。
  • 互斥(mutex):
    • 提供對共享資源的互斥訪問的鎖。 互斥鎖一次只能由一個線程持有拉鹃。試圖獲取由不同線程持有的互斥對象會使當前線程處于休眠狀態(tài)辈赋,直到最終獲得鎖為止鲫忍。
  • 進程(process):
    • 應用軟件或程序的運行時實例。 進程有自己的虛擬內(nèi)存空間和系統(tǒng)資源(包括端口權(quán)限) 钥屈,這些資源獨立于分配給其他程序的資源悟民。一個進程總是包含至少一個線程(主線程) ,并且可能包含任意數(shù)量的其他線程焕蹄。
  • 信號量(semaphore):
    • 限制對共享資源訪問的受保護變量逾雄。 互斥(Mutexes)和條件(conditions)都是不同類型的信號量。
  • 任務(task)腻脏,表示需要執(zhí)行的工作量鸦泳。
  • 線程(thread):
    • 進程中的執(zhí)行流程。 每個線程都有自己的堆椨榔罚空間做鹰,但在其他方面與同一進程中的其他線程共享內(nèi)存。
  • 運行循環(huán)(run loop):
    • 一個事件處理循環(huán)鼎姐, 接收事件并派發(fā)到適當?shù)奶幚沓绦颉?/li>

文末推薦:iOS熱門文集&視頻解析

① Swift

② iOS底層技術(shù)

③ iOS逆向防護

④ iOS面試合集

喜歡的小伙伴記得點贊喔~

收藏等于白嫖钾麸,點贊才是真情?( ′???` )?

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市炕桨,隨后出現(xiàn)的幾起案子饭尝,更是在濱河造成了極大的恐慌,老刑警劉巖献宫,帶你破解...
    沈念sama閱讀 206,482評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件钥平,死亡現(xiàn)場離奇詭異,居然都是意外死亡姊途,警方通過查閱死者的電腦和手機涉瘾,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,377評論 2 382
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來捷兰,“玉大人立叛,你說我怎么就攤上這事」泵” “怎么了秘蛇?”我有些...
    開封第一講書人閱讀 152,762評論 0 342
  • 文/不壞的土叔 我叫張陵,是天一觀的道長友扰。 經(jīng)常有香客問我彤叉,道長,這世上最難降的妖魔是什么村怪? 我笑而不...
    開封第一講書人閱讀 55,273評論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮浮庐,結(jié)果婚禮上甚负,老公的妹妹穿的比我還像新娘柬焕。我一直安慰自己,他們只是感情好梭域,可當我...
    茶點故事閱讀 64,289評論 5 373
  • 文/花漫 我一把揭開白布斑举。 她就那樣靜靜地躺著,像睡著了一般病涨。 火紅的嫁衣襯著肌膚如雪富玷。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,046評論 1 285
  • 那天既穆,我揣著相機與錄音赎懦,去河邊找鬼。 笑死幻工,一個胖子當著我的面吹牛励两,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播囊颅,決...
    沈念sama閱讀 38,351評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼当悔,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了踢代?” 一聲冷哼從身側(cè)響起盲憎,我...
    開封第一講書人閱讀 36,988評論 0 259
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎胳挎,沒想到半個月后饼疙,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,476評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡串远,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,948評論 2 324
  • 正文 我和宋清朗相戀三年宏多,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片澡罚。...
    茶點故事閱讀 38,064評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡伸但,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出留搔,到底是詐尸還是另有隱情更胖,我是刑警寧澤,帶...
    沈念sama閱讀 33,712評論 4 323
  • 正文 年R本政府宣布隔显,位于F島的核電站却妨,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏括眠。R本人自食惡果不足惜彪标,卻給世界環(huán)境...
    茶點故事閱讀 39,261評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望掷豺。 院中可真熱鬧捞烟,春花似錦薄声、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,264評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至苍息,卻和暖如春缩幸,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背竞思。 一陣腳步聲響...
    開封第一講書人閱讀 31,486評論 1 262
  • 我被黑心中介騙來泰國打工表谊, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人衙四。 一個月前我還...
    沈念sama閱讀 45,511評論 2 354
  • 正文 我出身青樓铃肯,卻偏偏與公主長得像,于是被迫代替她去往敵國和親传蹈。 傳聞我的和親對象是個殘疾皇子押逼,可洞房花燭夜當晚...
    茶點故事閱讀 42,802評論 2 345

推薦閱讀更多精彩內(nèi)容