Swift5 多線程 - GCD

在軟件開(kāi)發(fā)中,我們經(jīng)常會(huì)遇到大量計(jì)算画畅,或者數(shù)據(jù)很多的下載,這些都需要異步線程處理宋距。還有一些有先后順序的操作轴踱,這些要到隊(duì)列的處理。另外乡革,App 的 UI 更新也必須要回到主線程中處理寇僧。Swift 實(shí)現(xiàn)多線程的方法主要有三種 NSThreadNSOperationQueue沸版、GCD,不過(guò)我們通常用得比較多的是 GCD 方式

GCD(Grand Central Dispatch)

Grand Central Dispatch (GCD) 是 Apple 開(kāi)發(fā)的一個(gè)多核編程的解決方法嘁傀,基本概念就是dispatch queue(調(diào)度隊(duì)列),queue 是一個(gè)對(duì)象视粮,它可以接受任務(wù)细办,并將任務(wù)以先到先執(zhí)行的順序來(lái)執(zhí)行。dispatch queue 可以是并發(fā)的或串行的。GCD 的底層依然是用線程實(shí)現(xiàn)笑撞,不過(guò)我們可以不用關(guān)注實(shí)現(xiàn)的細(xì)節(jié)岛啸。Swift3 之前 GCD 仍是面向過(guò)程的寫法,所以需要封裝一層再使用茴肥。Swift3 蘋果打成Dispatch 這個(gè) module.你可以通過(guò) import 進(jìn)行導(dǎo)入再使用坚踩。Swift4 之后,我們可以直接使用瓤狐。其優(yōu)點(diǎn)有如下幾點(diǎn):

  1. 易用:GCD 比 thread更簡(jiǎn)單易用瞬铸。基于 block 的特效使它能極為簡(jiǎn)單地在不同代碼作用域之間傳遞上下文础锐。
  2. 效率:GCD 實(shí)現(xiàn)功能輕量嗓节,優(yōu)雅,使得它在很多地方比專門創(chuàng)建消耗資源的線程更加實(shí)用且快捷皆警。
  3. 性能:GCD 自動(dòng)根據(jù)系統(tǒng)負(fù)載來(lái)增減線程數(shù)量拦宣,從而減少了上下文切換并增加了計(jì)算效率。
  4. 安全:無(wú)需加鎖或其他同步機(jī)制信姓。

GCD 可用于多核的并行運(yùn)算
GCD 會(huì)自動(dòng)利用更多的CPU內(nèi)核(比如雙核鸵隧、四核)
GCD 會(huì)自動(dòng)管理線程的生命周期(創(chuàng)建線程、調(diào)度任務(wù)财破、銷毀線程)

隊(duì)列和任務(wù)

* GCD 中有2個(gè)核心概念
    任務(wù):執(zhí)行什么操作
    隊(duì)列:用來(lái)存放任務(wù)

* GCD 的使用就2個(gè)步驟
    定制任務(wù)
    確定想做的事情

* 將任務(wù)添加到隊(duì)列中
    GCD 會(huì)自動(dòng)將隊(duì)列中的任務(wù)取出掰派,放到對(duì)應(yīng)的線程中執(zhí)行
    任務(wù)的取出遵循隊(duì)列的 FIFO 原則:先進(jìn)先出从诲,后進(jìn)后出

DispatchQueue

Dispatch 會(huì)自動(dòng)的根據(jù) CPU 的使用情況左痢,創(chuàng)建線程來(lái)執(zhí)行任務(wù),并且自動(dòng)的運(yùn)行到多核上系洛,提高程序的運(yùn)行效率俊性。對(duì)于開(kāi)發(fā)者來(lái)說(shuō),在 GCD 層面是沒(méi)有線程的概念的描扯,只有隊(duì)列(queue)定页。任務(wù)都是以block 的方式提交到對(duì)列上,然后 GCD 會(huì)自動(dòng)的創(chuàng)建線程池去執(zhí)行這些任務(wù)绽诚。

DispatchQueue 是一個(gè)類似線程的概念典徊,這里稱作對(duì)列隊(duì)列是一個(gè) FIFO(先進(jìn)先出,后進(jìn)后出) 數(shù)據(jù)結(jié)構(gòu)恩够,意味著先提交到隊(duì)列的任務(wù)會(huì)先開(kāi)始執(zhí)行卒落。DispatchQueue 背后是一個(gè)由系統(tǒng)管理的線程池。

* 同步和異步的區(qū)別
    同步(`sync`):只能在當(dāng)前線程中執(zhí)行任務(wù)蜂桶,不具備開(kāi)啟新線程的能力
    異步 (`async`):可以在新的線程中執(zhí)行任務(wù)儡毕,具備開(kāi)啟新線程的能力

各種隊(duì)列的執(zhí)行效果

并發(fā)隊(duì)列 手動(dòng)創(chuàng)建的串行隊(duì)列 主隊(duì)列
同步(sync) 沒(méi)有開(kāi)啟新線程、串行執(zhí)行任務(wù) 沒(méi)有開(kāi)啟新線程扑媚、串行執(zhí)行任務(wù) 沒(méi)有開(kāi)啟新線程腰湾、串行執(zhí)行任務(wù)
異步(async) 有開(kāi)啟新線程雷恃、并發(fā)執(zhí)行任務(wù) 有開(kāi)啟新線程、串行執(zhí)行任務(wù) 沒(méi)有開(kāi)啟新線费坊、串行執(zhí)行任務(wù)
基本寫法倒槐,異步執(zhí)行回主線程寫法
    DispatchQueue.global().async {
        print("異步做某事: \(Thread.current)")
        DispatchQueue.main.async {
            print("回到主線程: \(Thread.current)")
        }
    }

DispatchQueue

一個(gè)對(duì)象,用于在應(yīng)用程序的主線程或后臺(tái)線程上串行或并發(fā)地管理任務(wù)的執(zhí)行附井。

    let queue = DispatchQueue(label: "labelname", qos: .default, attributes: .concurrent, autoreleaseFrequency: .inherit)
  • label 隊(duì)列的標(biāo)識(shí)符导犹,方便調(diào)試
  • qos 隊(duì)列的 quality of service。用來(lái)指明隊(duì)列的 “重要性”羡忘,后文會(huì)詳細(xì)講到谎痢。
  • attributes 隊(duì)列的屬性。類型是DispatchQueue.Attributes,是一個(gè)結(jié)構(gòu)體卷雕,遵循了協(xié)議OptionSet节猿。意味著你可以這樣傳入第一個(gè)參數(shù)[.option1,.option2]
  • autoreleaseFrequency。顧名思義漫雕,自動(dòng)釋放頻率滨嘱。有些隊(duì)列是會(huì)在執(zhí)行完任務(wù)后自動(dòng)釋放的,有些比如 Timer 等是不會(huì)自動(dòng)釋放的浸间,是需要手動(dòng)釋放太雨。

隊(duì)列分類

* 系統(tǒng)創(chuàng)建的隊(duì)列
    * 主隊(duì)列(對(duì)應(yīng)主線程)
    * 全局隊(duì)列
* 用戶創(chuàng)建的隊(duì)列
* 串行與并行
    // 主隊(duì)列 
    let mainQueue = DispatchQueue.main
    // 全局隊(duì)列
    let globalQueue = DispatchQueue.global()
    // 用戶創(chuàng)建的隊(duì)列
    let globalQueueWithQos = DispatchQueue.global(qos: .userInitiated)

串行與并行

串行(serial) :一次只能干一件事,挨個(gè)按順序執(zhí)行


串行.jpg

并行(concurrent):多條流水線同時(shí)工作


并行.jpg
    /*
        默認(rèn):列隊(duì)是串行的
        .concurrent:列隊(duì)是并發(fā)的
        .initiallyInactive:列隊(duì)不會(huì)自動(dòng)執(zhí)行魁蒜,需要開(kāi)發(fā)中手動(dòng)觸發(fā)
    */
    // 串行隊(duì)列 
    let serialQueue = DispatchQueue(label: "serialQueue")
    // 并行隊(duì)列
    let concurrentQueue = DispatchQueue(label: "concurrentQueue",attributes:.concurrent)

異步與同步

  • 異步(async)提交一段任務(wù)到隊(duì)列囊扳,并且立刻返回
    func serial() {
        // 異步
        let serialQueue = DispatchQueue(label: "serialQueue")
                print("\(Thread.current) Main queue Start")
                serialQueue.async {
                    self.readDataTask(label: "1")
                }
                serialQueue.async {
                    self.readDataTask(label: "2")
                }
                print("\(Thread.current) Main queue End")
    }
    
    // 讀數(shù)據(jù)
    func readDataTask(label: String){
        print("\(Thread.current) Start sync task\(label)")
        sleep(2)
        print("\(Thread.current) End sync task\(label)")
    }
    
/* 輸出結(jié)果
<NSThread: 0x6000021badc0>{number = 1, name = main} Main queue Start
<NSThread: 0x6000021e0cc0>{number = 4, name = (null)} Start sync task1
<NSThread: 0x6000021badc0>{number = 1, name = main} Main queue End
<NSThread: 0x6000021e0cc0>{number = 4, name = (null)} End sync task1
<NSThread: 0x6000021e0cc0>{number = 4, name = (null)} Start sync task2
<NSThread: 0x6000021e0cc0>{number = 4, name = (null)} End sync task2
    */

系統(tǒng)在導(dǎo)步串行隊(duì)列中開(kāi)了一個(gè)線程4處理,可以看到每一條線程兜看,是完成 task1 之后才會(huì)開(kāi)始 task2锥咸,而且主線程線程1沒(méi)有阻塞的運(yùn)行完。
總結(jié)一下细移,主線程按照順序提交任務(wù)1搏予,任務(wù)2到 serialQueue,瞬間執(zhí)行完畢弧轧,并沒(méi)有被阻塞雪侥。
在 serialQueue 上先執(zhí)行任務(wù)1,任務(wù)1執(zhí)行完畢后再執(zhí)行任務(wù)2.

  • 同步 (sync) 提交一段任務(wù)到隊(duì)列精绎,并且阻塞當(dāng)前線程速缨,任務(wù)結(jié)束后當(dāng)前線程繼續(xù)執(zhí)行
    func concurrent() {
        print("\(Thread.current) Main queue Start")
        // 同步
        let concurrentQueue = DispatchQueue(label: "concurrent", attributes: .concurrent)
        concurrentQueue.async {
            self.readDataTask(label: "3")
        }
        concurrentQueue.async {
            self.readDataTask(label: "4")
        }
        print("\(Thread.current) Main queue End")
    }
    
    // 讀數(shù)據(jù)
    func readDataTask(label: String){
        print("\(Thread.current) Start sync task\(label)")
        sleep(2)
        print("\(Thread.current) End sync task\(label)")
    }
    
    /* 輸出結(jié)果
<NSThread: 0x60000100cfc0>{number = 1, name = main} Main queue Start
<NSThread: 0x60000100cfc0>{number = 1, name = main} Main queue End
<NSThread: 0x600001040680>{number = 4, name = (null)} Start sync task4
<NSThread: 0x60000107ad80>{number = 5, name = (null)} Start sync task3
<NSThread: 0x60000107ad80>{number = 5, name = (null)} End sync task3
<NSThread: 0x600001040680>{number = 4, name = (null)} End sync task4
    */

主線程依然沒(méi)有被阻塞。
在 concurrentQueue 隊(duì)列上捺典,兩個(gè)任務(wù)按照提交的次序開(kāi)始鸟廓,兩個(gè)任務(wù)并發(fā)的執(zhí)行了。

sync 是一個(gè)強(qiáng)大但是容易被忽視的函數(shù)。使用 sync引谜,可以方便的進(jìn)行線程間同步牍陌。但是,有一點(diǎn)要注意员咽,sync 容易造成死鎖.

DispatchQoS (quality of service) 服務(wù)質(zhì)量

適用于任務(wù)的服務(wù)質(zhì)量或執(zhí)行優(yōu)先級(jí)毒涧。

優(yōu)先級(jí)由最低的 background 到最高的 userInteractive 共五個(gè),還有一個(gè)為定義的 unspecified.

  • background:最低優(yōu)先級(jí)贝室,等同于 DISPATCH_QUEUE_PRIORITY_BACKGROUND. 用戶不可見(jiàn)契讲,比如:在后臺(tái)存儲(chǔ)大量數(shù)據(jù)
  • utility:優(yōu)先級(jí)等同于 DISPATCH_QUEUE_PRIORITY_LOW,可以執(zhí)行很長(zhǎng)時(shí)間滑频,再通知用戶結(jié)果捡偏。比如:下載一個(gè)大文件,網(wǎng)絡(luò)峡迷,計(jì)算
  • default:默認(rèn)優(yōu)先級(jí),優(yōu)先級(jí)等同于 DISPATCH_QUEUE_PRIORITY_DEFAULT银伟,建議大多數(shù)情況下使用默認(rèn)優(yōu)先級(jí)
  • userInitiated:優(yōu)先級(jí)等同于 DISPATCH_QUEUE_PRIORITY_HIGH,需要立刻的結(jié)果
  • userInteractive:用戶交互相關(guān),為了好的用戶體驗(yàn)绘搞,任務(wù)需要立馬執(zhí)行彤避。使用該優(yōu)先級(jí)用于 UI 更新,事件處理和小工作量任務(wù)夯辖,在主線程執(zhí)行

Qos指定了列隊(duì)工作的優(yōu)先級(jí)琉预,系統(tǒng)會(huì)根據(jù)優(yōu)先級(jí)來(lái)調(diào)度工作,越高的優(yōu)先級(jí)能夠越快被執(zhí)行蒿褂,但是也會(huì)消耗功能圆米,所以準(zhǔn)確的指定優(yōu)先級(jí)能夠保證app有效的使用資源。

QoS 可以在創(chuàng)建 queue 時(shí)添加或者在提交 block 的時(shí)候贮缅,指定 QoS

    DispatchQueue.global().async(qos: .background) {
          // code
    }

DispatchWorkItem

想要執(zhí)行的工作以某種方式進(jìn)行封裝榨咐,使您可以附加完成句柄或執(zhí)行依賴項(xiàng)。通俗的說(shuō)就是 DispatchWorkItem 把任務(wù)封裝成一個(gè)對(duì)象谴供。

    let item = DispatchWorkItem {
        // 任務(wù)
    }
    DispatchQueue.global().async(execute: item)

也可以初始化時(shí)指定更多的參數(shù)

    DispatchWorkItem(qos: .userInitiated, flags: [.assignCurrentContext,.barrier]) {
            // 任務(wù)
    }
  • 第一個(gè)參數(shù)表示 QoS。
  • 第二個(gè)參數(shù)類型為 DispatchWorkItemFlags齿坷。指定這個(gè)任務(wù)的配飾信息
  • 第三個(gè)參數(shù)則是實(shí)際的任務(wù) block

DispatchWorkItemFlags 的參數(shù)分為兩組

執(zhí)行情況

  • barrier 屏障桂肌,后面詳解
  • detached
  • assignCurrentContext

QoS覆蓋信息

  • noQoS // 沒(méi)有QoS
  • inheritQoS // 繼承Queue的QoS
  • enforceQoS // 自己的QoS覆蓋Queue

DispatchGroup

DispatchGroup 用于管理一組任務(wù)的執(zhí)行,然后監(jiān)聽(tīng)任務(wù)的完成永淌,進(jìn)而執(zhí)行后續(xù)操作崎场。比如:同一個(gè)頁(yè)面發(fā)送多個(gè)網(wǎng)絡(luò)請(qǐng)求,等待所有結(jié)果請(qǐng)求成功刷新 UI 界面

notify(依賴任務(wù))

    func groupNotify() {
        let queue = DispatchQueue.global()
        let group = DispatchGroup()
        queue.async(group: group, qos: .default, flags: [], execute: {
            for _ in 0...4 {
                print("\(Thread.current) 耗時(shí)任務(wù)一")
            }
        })
        queue.async(group: group, qos: .default, flags: [], execute: {
            for _ in 0...4 {
                print("\(Thread.current) 耗時(shí)任務(wù)二")
            }
        })
        // 執(zhí)行完上面的兩個(gè)耗時(shí)操作, 回到 queue 隊(duì)列中執(zhí)行下一步的任務(wù)
        group.notify(queue: queue) {
            print("\(Thread.current) 回到該隊(duì)列中執(zhí)行")
        }
    }
    
    /* 輸出結(jié)果
<NSThread: 0x600002001800>{number = 3, name = (null)} 耗時(shí)任務(wù)一
<NSThread: 0x60000203ec40>{number = 5, name = (null)} 耗時(shí)任務(wù)二
<NSThread: 0x600002001800>{number = 3, name = (null)} 耗時(shí)任務(wù)一
<NSThread: 0x600002001800>{number = 3, name = (null)} 耗時(shí)任務(wù)一
<NSThread: 0x60000203ec40>{number = 5, name = (null)} 耗時(shí)任務(wù)二
<NSThread: 0x600002001800>{number = 3, name = (null)} 耗時(shí)任務(wù)一
<NSThread: 0x600002001800>{number = 3, name = (null)} 耗時(shí)任務(wù)一
<NSThread: 0x60000203ec40>{number = 5, name = (null)} 耗時(shí)任務(wù)二
<NSThread: 0x60000203ec40>{number = 5, name = (null)} 耗時(shí)任務(wù)二
<NSThread: 0x60000203ec40>{number = 5, name = (null)} 耗時(shí)任務(wù)二
<NSThread: 0x60000203ec40>{number = 5, name = (null)} 回到該隊(duì)列中執(zhí)行
    */

wait(任務(wù)等待)

    func groupWait() {
        let queue = DispatchQueue.global()
        let group = DispatchGroup()
        queue.async(group: group, qos: .default, flags: [], execute: {
            for _ in 0...4 {
                print("\(Thread.current) 耗時(shí)任務(wù)一")
            }
        })
        queue.async(group: group, qos: .default, flags: [], execute: {
            for _ in 0...4 {
                print("\(Thread.current) 耗時(shí)任務(wù)二")
                sleep(1)
            }
        })
        // 等待上面任務(wù)執(zhí)行遂蛀,會(huì)阻塞當(dāng)前線程谭跨,超時(shí)就執(zhí)行下面的,上面的繼續(xù)執(zhí)行◇χ妫可以無(wú)限等待 .distantFuture
        let result = group.wait(timeout: .now()+10)
        switch result {
        case .success:
            print("\(Thread.current) 不超時(shí), 上面的兩個(gè)任務(wù)都執(zhí)行完")
        case .timedOut:
            print("\(Thread.current) 超時(shí)了, 上面的任務(wù)還沒(méi)執(zhí)行完執(zhí)行這了")
        }
        
        print("\(Thread.current) 完成...")
    }
    
    /* 輸出結(jié)果
<NSThread: 0x600002084a80>{number = 3, name = (null)} 耗時(shí)任務(wù)一
<NSThread: 0x600002083700>{number = 5, name = (null)} 耗時(shí)任務(wù)二
<NSThread: 0x600002084a80>{number = 3, name = (null)} 耗時(shí)任務(wù)一
<NSThread: 0x600002084a80>{number = 3, name = (null)} 耗時(shí)任務(wù)一
<NSThread: 0x600002084a80>{number = 3, name = (null)} 耗時(shí)任務(wù)一
<NSThread: 0x600002084a80>{number = 3, name = (null)} 耗時(shí)任務(wù)一
<NSThread: 0x600002083700>{number = 5, name = (null)} 耗時(shí)任務(wù)二
<NSThread: 0x600002083700>{number = 5, name = (null)} 耗時(shí)任務(wù)二
<NSThread: 0x600002083700>{number = 5, name = (null)} 耗時(shí)任務(wù)二
<NSThread: 0x600002083700>{number = 5, name = (null)} 耗時(shí)任務(wù)二
<NSThread: 0x6000020da1c0>{number = 1, name = main} 不超時(shí), 上面的兩個(gè)任務(wù)都執(zhí)行完
<NSThread: 0x6000020da1c0>{number = 1, name = main} 完成...
    */

手動(dòng)管理 group 的計(jì)數(shù)器蛮瞄,enterleave 必須配對(duì)

由于是并發(fā)執(zhí)行異步任務(wù),所以任務(wù)的先后次序是不一定的谆扎,看起來(lái)符合我們的需求挂捅,最后接受通知然后可以刷新 UI 操作。但是真實(shí)的網(wǎng)絡(luò)請(qǐng)求是異步堂湖、耗時(shí)的闲先,并不是立馬就返回,所以我們使用 asyncAfter 模擬延時(shí)看看无蜂,將任務(wù)1延時(shí)一秒執(zhí)行:

   // 將任務(wù)1延時(shí)一秒執(zhí)行
   func groupNotify() {
        let queue = DispatchQueue.global()
        let group = DispatchGroup()
        queue.async(group: group, qos: .default, flags: [], execute: {
            // 增加耗時(shí)
            DispatchQueue.main.asyncAfter(deadline: .now() + 1, execute: {
                 for _ in 0...4 {
                     print("\(Thread.current) 耗時(shí)任務(wù)一")
                 }
            })
        })
        queue.async(group: group, qos: .default, flags: [], execute: {
            for _ in 0...4 {
                print("\(Thread.current) 耗時(shí)任務(wù)二")
            }
        })
        // 執(zhí)行完上面的兩個(gè)耗時(shí)操作, 回到 queue 隊(duì)列中執(zhí)行下一步的任務(wù)
        group.notify(queue: queue) {
            print("\(Thread.current) 回到該隊(duì)列中執(zhí)行")
        }
    }
    
    /* 輸出結(jié)果
<NSThread: 0x6000020aa9c0>{number = 6, name = (null)} 耗時(shí)任務(wù)二
<NSThread: 0x6000020aa9c0>{number = 6, name = (null)} 耗時(shí)任務(wù)二
<NSThread: 0x6000020aa9c0>{number = 6, name = (null)} 耗時(shí)任務(wù)二
<NSThread: 0x6000020aa9c0>{number = 6, name = (null)} 耗時(shí)任務(wù)二
<NSThread: 0x6000020aa9c0>{number = 6, name = (null)} 耗時(shí)任務(wù)二
<NSThread: 0x6000020a7f00>{number = 7, name = (null)} 回到該隊(duì)列中執(zhí)行
<NSThread: 0x6000020c8700>{number = 1, name = main} 耗時(shí)任務(wù)一
<NSThread: 0x6000020c8700>{number = 1, name = main} 耗時(shí)任務(wù)一
<NSThread: 0x6000020c8700>{number = 1, name = main} 耗時(shí)任務(wù)一
<NSThread: 0x6000020c8700>{number = 1, name = main} 耗時(shí)任務(wù)一
<NSThread: 0x6000020c8700>{number = 1, name = main} 耗時(shí)任務(wù)一
    */

所以伺糠,為了真正實(shí)現(xiàn)預(yù)期的效果,我們需要配合 groupenterleave 兩個(gè)函數(shù)斥季。每次執(zhí)行 group.enter() 表示一個(gè)任務(wù)被加入到列隊(duì)組 group 中退盯,此時(shí) group 中的任務(wù)的引用計(jì)數(shù)會(huì)加1,當(dāng)使用 group.leave() 泻肯,表示 group 中的一個(gè)任務(wù)完成渊迁,group 中任務(wù)的引用計(jì)數(shù)減1.當(dāng) group 列隊(duì)組里面的任務(wù)引用計(jì)數(shù)為0時(shí),會(huì)通知 notify 函數(shù)灶挟,任務(wù)執(zhí)行完成琉朽。

注意:enter()leave() 成對(duì)出現(xiàn)的。

    func enterLeaveGroup() {
        let group = DispatchGroup()
        let queue = DispatchQueue.global()
        
        // 把該任務(wù)添加到組隊(duì)列中執(zhí)行
        group.enter()
        queue.async(group: group, qos: .default, flags: []) {
            // 增加耗時(shí)
            DispatchQueue.main.asyncAfter(deadline: .now() + 1, execute: {
                 for _ in 0...4 {
                     print("\(Thread.current) 耗時(shí)任務(wù)一")
                 }
                // 執(zhí)行完之后從組隊(duì)列中移除
                group.leave()
            })
            
        }
        
        // 把該任務(wù)添加到組隊(duì)列中執(zhí)行
        group.enter()
        queue.async(group: group, qos: .default, flags: []) {
            for _ in 0...4 {
                print("\(Thread.current) 耗時(shí)任務(wù)二")
            }
            // 執(zhí)行完之后從組隊(duì)列中移除
            group.leave()
        }
        
        // 當(dāng)上面所有的任務(wù)執(zhí)行完之后通知
        group.notify(queue: queue) {
            print("\(Thread.current) 所有的任務(wù)執(zhí)行完了")
        }
    }
    
    /* 輸出結(jié)果
<NSThread: 0x6000013dd980>{number = 6, name = (null)} 耗時(shí)任務(wù)二
<NSThread: 0x6000013dd980>{number = 6, name = (null)} 耗時(shí)任務(wù)二
<NSThread: 0x6000013dd980>{number = 6, name = (null)} 耗時(shí)任務(wù)二
<NSThread: 0x6000013dd980>{number = 6, name = (null)} 耗時(shí)任務(wù)二
<NSThread: 0x6000013dd980>{number = 6, name = (null)} 耗時(shí)任務(wù)二
<NSThread: 0x6000013862c0>{number = 1, name = main} 耗時(shí)任務(wù)一
<NSThread: 0x6000013862c0>{number = 1, name = main} 耗時(shí)任務(wù)一
<NSThread: 0x6000013862c0>{number = 1, name = main} 耗時(shí)任務(wù)一
<NSThread: 0x6000013862c0>{number = 1, name = main} 耗時(shí)任務(wù)一
<NSThread: 0x6000013862c0>{number = 1, name = main} 耗時(shí)任務(wù)一
<NSThread: 0x6000013ea2c0>{number = 7, name = (null)} 所有的任務(wù)執(zhí)行完了
    */

asyncAfter 延時(shí)處理

使用 asyncAfter 來(lái)提交任務(wù)進(jìn)行延遲稚铣。之前是使用 dispatch_time,現(xiàn)在是使用 DispatchTime 對(duì)象表示箱叁。可以使用靜態(tài)方法 now 獲得當(dāng)前時(shí)間惕医,然后再通過(guò)加上 DispatchTimeInterval 枚舉獲得一個(gè)需要延遲的時(shí)間耕漱。注意:僅僅是用于在具體時(shí)間執(zhí)行任務(wù),不要在資源競(jìng)爭(zhēng)的情況下使用抬伺。并且在主列隊(duì)使用螟够。

let delay = DispatchTime.now() + DispatchTimeInterval.seconds(10)
        DispatchQueue.main.asyncAfter(deadline: delay) {
            // 延遲執(zhí)行
        }

因?yàn)樵?DispatchTime 中自定義了“+”號(hào), 我們進(jìn)一步簡(jiǎn)化

public func +(time: DispatchTime, seconds: Double) -> DispatchTime

let delay = DispatchTime.now() + 10
        DispatchQueue.main.asyncAfter(deadline: delay) {
            // 延遲執(zhí)行
        }

DispatchSemaphore 信號(hào)量

Semaphore 是保證線程安全的一種方式,而且繼 OSSpinLock 不再安全后峡钓,Semaphore 似乎成為了最快的加鎖的方式妓笙。

信號(hào)量在多線程開(kāi)發(fā)中被廣泛使用,當(dāng)一個(gè)線程在進(jìn)入一段關(guān)鍵代碼之前能岩,線程必須獲取一個(gè)信號(hào)量寞宫,一旦該關(guān)鍵代碼段完成了,那么該線程必須釋放信號(hào)量拉鹃。其它想進(jìn)入該關(guān)鍵代碼段的線程必須等待前面的線程釋放信號(hào)量辈赋。

信號(hào)量的具體做法是:當(dāng)信號(hào)計(jì)數(shù)大于0時(shí)鲫忍,每條進(jìn)來(lái)的線程使計(jì)數(shù)減1,直到變?yōu)?钥屈,變?yōu)?后其他的線程將進(jìn)不來(lái)悟民,處于等待狀態(tài);執(zhí)行完任務(wù)的線程釋放信號(hào)焕蹄,使計(jì)數(shù)加1逾雄,如此循環(huán)下去。

    func semaphore() {
        // 創(chuàng)建信號(hào)量腻脏,參數(shù):信號(hào)量的初值鸦泳,如果小于0則會(huì)返回 NULL, value 表示最多幾個(gè)資源可訪問(wèn)
        let semaphore = DispatchSemaphore(value: 2)
        let queue = DispatchQueue.global()
        // 任務(wù)1
        queue.async {
            // 等待降低信號(hào)量
            semaphore.wait()
            print("運(yùn)行任務(wù)1")
            sleep(1)
            print("結(jié)果任務(wù)1")
            // 提高信號(hào)量
            semaphore.signal()
        }
        // 任務(wù)2
        queue.async {
            // 等待降低信號(hào)量
            semaphore.wait()
            print("運(yùn)行任務(wù)2")
            sleep(1)
            print("結(jié)果任務(wù)2")
            // 提高信號(hào)量
            semaphore.signal()
        }
        // 任務(wù)3
        queue.async {
            // 等待降低信號(hào)量
            semaphore.wait()
            print("運(yùn)行任務(wù)3")
            sleep(1)
            print("結(jié)果任務(wù)3")
            // 提高信號(hào)量
            semaphore.signal()
        }
    }
    
    /* 輸出結(jié)果:
運(yùn)行任務(wù)2
運(yùn)行任務(wù)1
結(jié)果任務(wù)1
結(jié)果任務(wù)2
運(yùn)行任務(wù)3
結(jié)果任務(wù)3
    */

由于設(shè)定的信號(hào)值為2,先執(zhí)行兩個(gè)線程永品,等執(zhí)行完一個(gè)做鹰,才會(huì)繼續(xù)執(zhí)行下一個(gè),保證同一時(shí)間執(zhí)行的線程數(shù)不超過(guò)2鼎姐。

Barrier 屏障

GCD 里的 BarrierNSOperationQueuedependency 比較接近钾麸,C 任務(wù)開(kāi)始之前需要 A 任務(wù)完成,或者 A 和 B 任務(wù)完成炕桨。

    func barrier() {
        let queue = DispatchQueue(label: "barrier", attributes: .concurrent)
        queue.async {
            print("任務(wù)A")
            
        }
        queue.async {
            sleep(3)
            print("任務(wù)B")
        }
        // 這里 barrier饭尝,必須等任務(wù)C完成后,才走后面任務(wù)D
        queue.async(flags: .barrier) {
            sleep(2)
            print("任務(wù)C")
        }
        queue.async {
            print("任務(wù)D")
        }
    }

假如我們有一個(gè)并發(fā)的列隊(duì)用來(lái)讀寫一個(gè)數(shù)據(jù)對(duì)象献宫,如果這個(gè)列隊(duì)的操作是讀钥平,那么可以同時(shí)多個(gè)進(jìn)行。如果有寫的操作姊途,則必須保證在執(zhí)行寫操作時(shí)涉瘾,不會(huì)有讀取的操作執(zhí)行,必須等待寫操作完成之后再開(kāi)始讀取操作捷兰,否則會(huì)造成讀取的數(shù)據(jù)出錯(cuò)立叛,經(jīng)典的讀寫問(wèn)題。這里我們就可以使用 barrier, 通過(guò)在并發(fā)代碼中使用 barrier 將能夠保證寫操作在所有讀取操作完成之后進(jìn)行贡茅,而且確保寫操作執(zhí)行完成之后再開(kāi)始后續(xù)的讀取操作秘蛇。

// 保證寫入時(shí),不能讀數(shù)據(jù)
let item = DispatchWorkItem(qos: .default, flags: .barrier) {
            // write data
        }
        let dataQueue = DispatchQueue(label: "queue", attributes: .concurrent)
        dataQueue.async(execute: item)

Suspend / Resume

Suspend 可以掛起一個(gè)線程友扰,即暫停線程彤叉,但是仍然暫用資源,只是不執(zhí)行

Resume 回復(fù)線程村怪,即繼續(xù)執(zhí)行掛起的線程。

循環(huán)執(zhí)行任務(wù)

調(diào)用 concurrentPerform()

        // 并發(fā)執(zhí)行5次
        DispatchQueue.concurrentPerform(iterations: 5) {
            print("\($0)")
        }

DispatchSource

DispatchSource提高了相關(guān)的API來(lái)監(jiān)控低級(jí)別的系統(tǒng)對(duì)象浮庐,比如:Mach ports, Unix descriptors, Unix signals, VFS nodes甚负。并且能夠異步提交事件到派發(fā)列隊(duì)執(zhí)行柬焕。

簡(jiǎn)單定時(shí)器

func timer() {
        // 定時(shí)時(shí)間
        var timeCount = 60
        // 創(chuàng)建時(shí)間源
        let timer = DispatchSource.makeTimerSource(queue: DispatchQueue.global())
        // 每秒調(diào)用一次
        timer.schedule(deadline: .now(), repeating: .seconds(1))
        timer.setEventHandler {
            timeCount -= 1
            if timeCount <= 0 { timer.cancel() }
            DispatchQueue.main.async {
                print("update UI or other task")
            }
        }
        // 啟動(dòng)時(shí)間源
        timer.resume()
    }

注意事項(xiàng)
線程死鎖
不要在主列隊(duì)中執(zhí)行同步任務(wù),這樣會(huì)造成死鎖問(wèn)題梭域。

參考文章:

Apple Documentation
Swift4 - GCD的使用
iOS多線程 Swift4 GCD深入解析
GCD精講(Swift 3&4)
iOS GCD中級(jí)篇 - dispatch_semaphore(信號(hào)量)的理解及使用

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末斑举,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子病涨,更是在濱河造成了極大的恐慌富玷,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,941評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件既穆,死亡現(xiàn)場(chǎng)離奇詭異赎懦,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)幻工,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,397評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門励两,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人囊颅,你說(shuō)我怎么就攤上這事当悔。” “怎么了踢代?”我有些...
    開(kāi)封第一講書人閱讀 165,345評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵盲憎,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我胳挎,道長(zhǎng)饼疙,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書人閱讀 58,851評(píng)論 1 295
  • 正文 為了忘掉前任串远,我火速辦了婚禮宏多,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘澡罚。我一直安慰自己伸但,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,868評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布留搔。 她就那樣靜靜地躺著更胖,像睡著了一般。 火紅的嫁衣襯著肌膚如雪隔显。 梳的紋絲不亂的頭發(fā)上却妨,一...
    開(kāi)封第一講書人閱讀 51,688評(píng)論 1 305
  • 那天,我揣著相機(jī)與錄音括眠,去河邊找鬼彪标。 笑死,一個(gè)胖子當(dāng)著我的面吹牛掷豺,可吹牛的內(nèi)容都是我干的捞烟。 我是一名探鬼主播薄声,決...
    沈念sama閱讀 40,414評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼题画!你這毒婦竟也來(lái)了默辨?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書人閱讀 39,319評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤苍息,失蹤者是張志新(化名)和其女友劉穎缩幸,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體竞思,經(jīng)...
    沈念sama閱讀 45,775評(píng)論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡表谊,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,945評(píng)論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了衙四。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片铃肯。...
    茶點(diǎn)故事閱讀 40,096評(píng)論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖传蹈,靈堂內(nèi)的尸體忽然破棺而出押逼,到底是詐尸還是另有隱情,我是刑警寧澤惦界,帶...
    沈念sama閱讀 35,789評(píng)論 5 346
  • 正文 年R本政府宣布挑格,位于F島的核電站,受9級(jí)特大地震影響沾歪,放射性物質(zhì)發(fā)生泄漏漂彤。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,437評(píng)論 3 331
  • 文/蒙蒙 一灾搏、第九天 我趴在偏房一處隱蔽的房頂上張望挫望。 院中可真熱鬧,春花似錦狂窑、人聲如沸媳板。這莊子的主人今日做“春日...
    開(kāi)封第一講書人閱讀 31,993評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)蛉幸。三九已至,卻和暖如春丛晦,著一層夾襖步出監(jiān)牢的瞬間奕纫,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書人閱讀 33,107評(píng)論 1 271
  • 我被黑心中介騙來(lái)泰國(guó)打工烫沙, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留匹层,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,308評(píng)論 3 372
  • 正文 我出身青樓锌蓄,卻偏偏與公主長(zhǎng)得像又固,于是被迫代替她去往敵國(guó)和親仲器。 傳聞我的和親對(duì)象是個(gè)殘疾皇子煤率,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,037評(píng)論 2 355

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