iOS 多線程記錄(二)

前言

關於這篇文章的Demo能夠去個人github中MultiThreadDemo查看源碼,若有不當之處,但願你們指出。git

這裏是個人上一篇關於多線程的知識點記錄 iOS 多線程記錄(一)程序員

GCD

介紹

GCD是蘋果開發的多線程編程的解決方案,經過簡單的API就能夠實現建立新線程去執行咱們須要執行的任務,不須要咱們手動地建立和管理線程,只須要建立隊列和相應的函數配合使用就行。github

優勢

  • GCD 可用於多核的並行運算
  • GCD 會自動利用更多的 CPU 內核(好比雙核、四核)
  • GCD 會自動管理線程的生命週期(建立線程、調度任務、銷燬線程)
  • 程序員只須要告訴 GCD 想要執行什麼任務,不須要編寫任何線程管理代碼

核心概念

  • 隊列

這裏的隊列指執行任務的等待隊列,即用來存聽任務的隊列。隊列是一種特殊的線性表,採用 FIFO(先進先出)的原則,即新任務老是被插入到隊列的末尾,而讀取任務的時候老是從隊列的頭部開始讀取。每讀取一個任務,則從隊列中釋放一個任務。編程

在 GCD 中有兩種隊列:串行隊列和併發隊列。二者的主要區別是:執行順序不一樣,以及開啓線程數不一樣bash

  • 串行隊列網絡

    • 每次只有一個任務被執行。讓任務一個接着一個地執行。(只開啓一個線程,一個任務執行完畢後,再執行下一個任務)
  • 併發隊列多線程

    • 可讓多個任務併發執行。(能夠開啓多個線程,而且同時執行任務)

併發隊列 的併發功能只有在異步函數下才有效。閉包

  • 任務

就是咱們須要執行的操做。執行任務有兩種方式:同步執行(sync)和異步執行(async)。二者的主要區別是:是否等待隊列的任務執行結束,以及是否具有開啓新線程的能力併發

  • 同步執行(sync)
    • 同步添加任務到指定的隊列中,會在前面的任務執行完成以後,再執行。
    • 只能在當前線程中執行任務,不具有開啓新線程的能力。
  • 異步執行(async)
    • 異步添加任務到指定的隊列中,它不會作任何等待,能夠直接執行任務。
    • 能夠在新的線程中執行任務,具有開啓新線程的能力。

基本使用

  1. 建立一個隊列
  2. 在隊列中添加任務

建立隊列

  • 獲取主隊列
// 獲取主隊列
let mainQueue = DispatchQueue.main
複製代碼
  • 獲取全局隊列
// 獲取全局隊列
let globalQueue = DispatchQueue.global()
複製代碼
  • 建立隊列
  1. 簡單方式建立

指定隊列的名稱,其它爲默認項,這樣的初始化的列隊有着默認的配置項,默認的列隊是串行列隊app

let queue = DispatchQueue(label: "com.jiangT.queue")
複製代碼
  1. 屬性設置方式建立
let queue = DispatchQueue.init(label: "com.jiangT.queue",
                                qos: DispatchQoS.default,
                                attributes:DispatchQueue.Attributes.concurrent,
                                autoreleaseFrequency:DispatchQueue.AutoreleaseFrequency.inherit,
                                target: nil)
複製代碼

參數介紹:

  • label: 隊列的標識符,可以方便區分列隊進行調試。
  • qos: 隊列的優先級。

優先級由最低的background到最高的userInteractive共五個,還有一個爲定義的unspecified.

background:最低優先級,等同於DISPATCH_QUEUE_PRIORITY_BACKGROUND.用戶不可見,好比:在後臺存儲大量數據

utility:優先級等同於DISPATCH_QUEUE_PRIORITY_LOW,能夠執行很長時間,再通知用戶結果。好比:下載一個大文件,網絡,計算

default:默認優先級,優先級等同於DISPATCH_QUEUE_PRIORITY_DEFAULT,建議大多數狀況下使用默認優先級

userInitiated:優先級等同於DISPATCH_QUEUE_PRIORITY_HIGH,須要馬上的結果

.userInteractive:用戶交互相關,爲了好的用戶體驗,任務須要立馬執行。使用該優先級用於UI更新,事件處理和小工做量任務,在主線程執行。

Qos指定了列隊工做的優先級,系統會根據優先級來調度工做,越高的優先級可以越快被執行,可是也會消耗功能,因此準確的指定優先級可以保證app有效的使用資源。

  • attributes: 隊列的屬性,能夠指定併發仍是串行。
  • autoreleaseFrequency: 自動釋放頻率,有些列隊會在執行完任務以後自動釋放,有些是不會自動釋放的,須要手動釋放。

添加任務

  • 同步任務
let globalQueue = DispatchQueue.global()

globalQueue.sync {
    print("sync + \(Thread.current)")
}
複製代碼
  • 異步任務
let globalQueue = DispatchQueue.global()

globalQueue.async {
    print("async + \(Thread.current)")
}
複製代碼

雖然使用 GCD 只需兩步,可是既然咱們有兩種隊列(串行隊列/併發隊列),兩種任務執行方式(同步執行/異步執行),那麼咱們就有了四種不一樣的組合方式。再加上兩種特殊隊列:全局併發隊列、主隊列。全局併發隊列能夠做爲普通併發隊列來使用。可是主隊列由於有點特殊,因此咱們就又多了兩種組合方式。這樣就有六種不一樣的組合方式了。

  1. 併發隊列 + 同步執行
  2. 併發隊列 + 異步執行
  3. 串行隊列 + 同步執行
  4. 串行隊列 + 異步執行
  5. 主隊列 + 同步執行
  6. 主隊列 + 異步執行

各類組合的結果

區別 併發隊列 串行隊列 主隊列
同步(sync) 沒有開啓新線程,串行執行任務 沒有開啓新線程,串行執行任務 會形成死鎖
異步(async) 有開啓新線程,併發執行任務 有開啓新線程(1條),串行執行任務 沒有開啓新線程,串行執行任務

併發隊列 + 同步執行

DispatchQueue.global().sync {
    print("sync1 + \(Thread.current)")
}
DispatchQueue.global().sync {
    print("sync2 + \(Thread.current)")
}

-----輸出結果:-----
sync1 + <NSThread: 0x600003bc35c0>{number = 1, name = main}
sync2 + <NSThread: 0x600003bc35c0>{number = 1, name = main}
複製代碼

結論:

  • 全部任務都是在當前線程(主線程)中執行的,沒有開啓新的線程(同步執行不具有開啓新線程的能力)。
  • 任務按順序執行的。按順序執行的緣由:雖然併發隊列能夠開啓多個線程,而且同時執行多個任務。可是同步任務不具有開啓新線程的能力,因此也就不存在併發。並且當前線程只有等待當前隊列中正在執行的任務執行完畢以後,才能繼續接着執行下面的操做(同步任務須要等待隊列的任務執行結束)。因此任務只能一個接一個按順序執行,不能同時被執行。

併發隊列 + 異步執行

DispatchQueue.global().async {
    print("begin")
    print("async1 + \(Thread.current)")
}
DispatchQueue.global().async {
    print("begin")
    print("async2 + \(Thread.current)")
}
DispatchQueue.global().async {
    print("begin")
    print("async3 + \(Thread.current)")
}
DispatchQueue.global().async {
    print("begin")
    print("async4 + \(Thread.current)")
}
DispatchQueue.global().async {
    print("begin")
    print("async5 + \(Thread.current)")
}

-----輸出結果:-----
begin
begin
begin
async1 + <NSThread: 0x60000018e540>{number = 3, name = (null)}
begin
async2 + <NSThread: 0x600000196600>{number = 4, name = (null)}
begin
async3 + <NSThread: 0x6000001bc900>{number = 5, name = (null)}
async5 + <NSThread: 0x600000196600>{number = 4, name = (null)}
async4 + <NSThread: 0x60000018e540>{number = 3, name = (null)}
複製代碼

結論:

  • 除了當前線程(主線程),系統又開啓了3個線程,而且任務是交替/同時執行的。(異步執行具有開啓新線程的能力。且併發隊列可開啓多個線程,同時執行多個任務)。

串行隊列 + 同步執行

let queue = DispatchQueue.init(label: "")

queue.sync {
    print("sync1 + \(Thread.current)")
}
queue.sync {
    print("sync1 + \(Thread.current)")
}

-----輸出結果:-----
sync1 + <NSThread: 0x600000a968c0>{number = 1, name = main}
sync2 + <NSThread: 0x600000a968c0>{number = 1, name = main}

複製代碼

結論:

  • 全部任務都是在當前線程(主線程)中執行的,並無開啓新的線程(同步執行不具有開啓新線程的能力)。
  • 任務是按順序執行的(串行隊列每次只有一個任務被執行,任務一個接一個按順序執行)。

串行隊列 + 異步執行

let queue = DispatchQueue.init(label: "")

queue.async {
    print("begin")
    print("async1 + \(Thread.current)")
}
queue.async {
    print("begin")
    print("async2 + \(Thread.current)")
}
queue.async {
    print("begin")
    print("async3 + \(Thread.current)")
}

-----輸出結果:-----
begin
async1 + <NSThread: 0x60000032b940>{number = 3, name = (null)}
begin
async2 + <NSThread: 0x60000032b940>{number = 3, name = (null)}
begin
async3 + <NSThread: 0x60000032b940>{number = 3, name = (null)}
複製代碼

結論:

  • 開啓了一條新線程(異步執行具有開啓新線程的能力,串行隊列只開啓一個線程)。
  • 任務是按順序執行的(串行隊列每次只有一個任務被執行,任務一個接一個按順序執行)。

主隊列 + 同步執行

  • 主隊列:GCD自帶的一種特殊的串行隊列
// 主線程執行同步任務
func syncMain() {
    print("方法開始")   // 調用方法後,能夠看到這個輸出
    DispatchQueue.main.sync {
        print("會形成死鎖")
    }
}

-----輸出結果:-----
方法開始
複製代碼

結論:

  1. 當把任務放進主隊列時,它須要等待主隊列執行完當前任務後執行。
  2. 主線程如今正在處理 syncMain 方法,任務須要等 syncMain 執行完才能執行。
  3. syncMain 在執行時,又要等任務執行完才能完成方法。
  4. 這樣 syncMain 方法和任務就開始了互相等待,造成了死鎖。

主隊列 + 異步執行

DispatchQueue.main.async {
    print("begin")
    print("async1 + \(Thread.current)")
}
DispatchQueue.main.async {
    print("begin")
    print("async2 + \(Thread.current)")
}
DispatchQueue.main.async {
    print("begin")
    print("async3 + \(Thread.current)")
}
DispatchQueue.main.async {
    print("begin")
    print("async4 + \(Thread.current)")
}
DispatchQueue.main.async {
    print("begin")
    print("async5 + \(Thread.current)")
}

-----輸出結果:-----
begin
async1 + <NSThread: 0x600001af6880>{number = 1, name = main}
begin
async2 + <NSThread: 0x600001af6880>{number = 1, name = main}
begin
async3 + <NSThread: 0x600001af6880>{number = 1, name = main}
begin
async4 + <NSThread: 0x600001af6880>{number = 1, name = main}
begin
async5 + <NSThread: 0x600001af6880>{number = 1, name = main}
複製代碼

結論:

  • 全部任務都是在當前線程(主線程)中執行的,並無開啓新的線程(雖然異步執行具有開啓線程的能力,但由於是主隊列,因此全部任務都在主線程中)。
  • 任務是按順序執行的(由於主隊列是串行隊列,每次只有一個任務被執行,任務一個接一個按順序執行)。

線程間通訊

/**
 * 線程間通訊
 */
DispatchQueue.global().async {
    print("async-begin")
    print("global + \(Thread.current)")
    print("async-end")
    
    let testNum = 666
    
    // 回到主線程
    DispatchQueue.main.async {
        print("main + \(Thread.current)")
        print("pass testNum: \(testNum)")
    }
}
-----輸出結果:-----
async-begin
global + <NSThread: 0x60000383d100>{number = 3, name = (null)}
async-end
main + <NSThread: 0x600003821680>{number = 1, name = main}
pass testNum: 666
複製代碼

結論:

  • 能夠看到在全局線程中先執行任務,執行完了以後回到主線程執行主線程的相應操做並將全局線程的testNum接收到。

DispatchWorkItem

DispatchWorkItem是用於幫助DispatchQueue來執行列隊中的任務。

通常狀況下,咱們開啓一個異步線程,會這樣建立列隊並執行async方法,以閉包的方式提交任務。

DispatchQueue.global().async {
    // do async task
}
複製代碼

使用了DispatchWorkItem類將任務封裝成爲對象,由對象進行任務。

let item = DispatchWorkItem {
      // do task
 }
 DispatchQueue.global().async(execute: item)
複製代碼

也可使用DispatchWorkItem實例對象的perform方法執行任務

let workItem = DispatchWorkItem {
     // do task
 }
 DispatchQueue.global().async {
    workItem.perform()
 }
複製代碼
柵欄方法

咱們有時須要異步執行兩組操做,並且第一組操做執行完以後,才能開始執行第二組操做。這樣咱們就須要一個至關於 柵欄 同樣的一個方法將兩組異步執行的操做組給分割起來,固然這裏的操做組裏能夠包含一個或多個任務。

柵欄 會等待前邊追加到併發隊列中的任務所有執行完畢以後,再將指定的任務追加到該異步隊列中。

let dataQueue = DispatchQueue(label: "com.jiangT.queue", attributes: .concurrent)

let item = DispatchWorkItem(qos: .default, flags: .barrier) {
    print("barrier + \(Thread.current)")
    dataQueue.async {
        print("async1 + \(Thread.current)")
    }
    dataQueue.async {
        print("async2 + \(Thread.current)")
    }
}

dataQueue.async(execute: item)
dataQueue.async {
    print("async3 + \(Thread.current)")
}
dataQueue.async {
    print("async4 + \(Thread.current)")
}

-----輸出結果:-----
barrier + <NSThread: 0x600000f9d800>{number = 3, name = (null)}
async3 + <NSThread: 0x600000f9da40>{number = 6, name = (null)}
async4 + <NSThread: 0x600000f9bac0>{number = 7, name = (null)}
async1 + <NSThread: 0x600000f9d800>{number = 3, name = (null)}
async2 + <NSThread: 0x600000fa4040>{number = 8, name = (null)}
複製代碼
執行任務結束經過nofify得到通知
let workItem = DispatchWorkItem {
    // do async task
    print(Thread.current)
}
DispatchQueue.global().async {
    workItem.perform()
}
workItem.notify(queue: DispatchQueue.main) {
    // update UI
    print(Thread.current)
}

-----輸出結果:-----
<NSThread: 0x60000193ce40>{number = 3, name = (null)}
<NSThread: 0x600001921100>{number = 1, name = main}
複製代碼
使用wait等待任務執行完成
let queue = DispatchQueue(label: "queue", attributes: .concurrent)
let workItem = DispatchWorkItem {
    sleep(5)
    print("done")
}

queue.async(execute: workItem)
print("before waiting")
workItem.wait()
print("after waiting")

-----輸出結果:-----
before waiting
done
after waiting
複製代碼

延遲執行方法

let delay = DispatchTime.now() + DispatchTimeInterval.seconds(10)
 DispatchQueue.main.asyncAfter(deadline: delay) {
     // 延遲執行
 }
 
 能夠簡化爲:
 
 let delay = DispatchTime.now() + 10
 DispatchQueue.main.asyncAfter(deadline: delay) {
     // 延遲執行
 }
複製代碼

其它的延時執行方法:

func asyncAfter(deadline: DispatchTime, execute: DispatchWorkItem)
func asyncAfter(deadline: DispatchTime, qos: DispatchQoS, flags: DispatchWorkItemFlags, execute: () -> Void)

func asyncAfter(wallDeadline: DispatchWallTime, execute: DispatchWorkItem)
func asyncAfter(wallDeadline: DispatchWallTime, qos: DispatchQoS, flags: DispatchWorkItemFlags, execute: () -> Void)
複製代碼

快速迭代方法

以前使用GCD的dispatch_apply()執行屢次任務,如今是調用concurrentPerform(),下面是併發執行5次

DispatchQueue.concurrentPerform(iterations: 5) {
    print("\($0)")
}

-----輸出結果:-----
2
0
1
3
4
複製代碼

隊列組操做

有時候咱們會有這樣的需求:分別異步執行2個耗時任務,而後當2個耗時任務都執行完畢後再回到主線程執行任務。這時候咱們能夠用到 GCD 的隊列組。

let queue = DispatchQueue.global()
let group = DispatchGroup()

group.enter()
queue.async(group: group) {
    DispatchQueue.main.asyncAfter(deadline: .now() + 2, execute: {
        print("Task one finished")
        group.leave()
    })
}
group.enter()
queue.async(group: group) {
    print("Task two finished")
    group.leave()
}
group.enter()
queue.async(group: group) {
    print("Task three finished")
    group.leave()
}
group.notify(queue: queue) {
    print("All task has finished")
}

----輸出結果:-----
Task two finished
Task three finished
Task one finished
All task has finished

複製代碼
相關文章
相關標籤/搜索