GCD(Swift)

原文鏈接

這篇文主要想總結(jié)下多線程在swift中的使用,先看下基本概念

進(jìn)程


進(jìn)程指在系統(tǒng)中能獨(dú)立運(yùn)行并作為資源分配的基本單位,它是由一組機(jī)器指令、數(shù)據(jù)和堆棧等組成的,是一個(gè)能獨(dú)立運(yùn)行的活動(dòng)實(shí)體

線程


線程是進(jìn)程的基本執(zhí)行單元,一個(gè)進(jìn)程(程序)的所有任務(wù)都在線程中執(zhí)行。

隊(duì)列


隊(duì)列,又稱為佇列(queue),是先進(jìn)先出(FIFO, First-In-First-Out)的線性表。在具體應(yīng)用中通常用鏈表或者數(shù)組來實(shí)現(xiàn)。隊(duì)列只允許在后端(稱為rear)進(jìn)行插入操作,在前端(稱為front)進(jìn)行刪除操作。隊(duì)列的操作方式和堆棧類似,唯一的區(qū)別在于隊(duì)列只允許新數(shù)據(jù)在后端進(jìn)行添加。

同步/異步


可以這么理解:(知乎)

  • 假如你要做兩件事 , 燒水 、 刷牙
  • 同步 :你燒水 , 等水燒開了你再去刷牙
  • 異步 :你燒水 ,不等水燒開就去刷牙了 , 水燒開了會發(fā)出聲音告訴你(callback) , 然后你再處理水燒開之后的事情

只要你是個(gè)正常人 , 都會選擇第二種 , 當(dāng)然也有特殊情況 , 那個(gè)人喜歡用熱水刷牙

并發(fā)


指兩個(gè)或多個(gè)事件在同一時(shí)間間隔內(nèi)發(fā)生??梢栽谀硹l線程和其他線程之間反復(fù)多次進(jìn)行上下文切換,看上去就好像一個(gè)CPU能夠并且執(zhí)行多個(gè)線程一樣。其實(shí)是偽異步。

線程隊(duì)列中并行/串行


串行隊(duì)列:串行隊(duì)列的特點(diǎn)是隊(duì)列內(nèi)的線程是一個(gè)一個(gè)執(zhí)行,直到結(jié)束。并行隊(duì)列:并行隊(duì)列的特點(diǎn)是隊(duì)列中所有線程的執(zhí)行結(jié)束時(shí)必須是一塊的,隊(duì)列中其他線程執(zhí)行完畢后,會阻塞當(dāng)前線程等待隊(duì)列中其他線程執(zhí)行,然后一塊執(zhí)行完畢。


如何使用


DispatchQueue.global().async {

    print("do something in global \(Thread.current)")

    DispatchQueue.main.async {

        print("do something in main \(Thread.current)")
    }
}

這里使用了全局的隊(duì)列執(zhí)行一些任務(wù) , 然后切回主隊(duì)列 , 這里要注意主隊(duì)列是運(yùn)行在主線程上的任務(wù)堆棧 。

自定義隊(duì)列

除了使用全局隊(duì)列外我們還可以使用自定義的隊(duì)列

let q = DispatchQueue(label: "com.felix.felix")

初始化一個(gè)隊(duì)列最簡單的方式就是聲明它的標(biāo)簽 。

async

打開Xcode,新建一個(gè)commandLineTool工程、
打開main.swift

let q = DispatchQueue(label: "com.felix.felix")

q.sync {
    (1...5).forEach({ i in
        print("?? \(Thread.current) + \(i)")
    })
}
q.async {
    (6...10).forEach({ i in
        print("?? \(Thread.current) + \(i)")
    })
}
(11...15).forEach({ i in
    print("?? \(Thread.current) + \(i)")
})


先聲明一個(gè)隊(duì)列,使用sync添加一個(gè)同步的任務(wù)輸出1到5,使用async異步輸出6到10,同時(shí)在主線程打印11到15 。

按下command+R運(yùn)行project,

?? <NSThread: 0x103103480>{number = 1, name = main} + 1
?? <NSThread: 0x103103480>{number = 1, name = main} + 2
?? <NSThread: 0x103103480>{number = 1, name = main} + 3
?? <NSThread: 0x103103480>{number = 1, name = main} + 4
?? <NSThread: 0x103103480>{number = 1, name = main} + 5
?? <NSThread: 0x103103480>{number = 1, name = main} + 11
?? <NSThread: 0x103007940>{number = 2, name = (null)} + 6
?? <NSThread: 0x103103480>{number = 1, name = main} + 12
?? <NSThread: 0x103007940>{number = 2, name = (null)} + 7
?? <NSThread: 0x103103480>{number = 1, name = main} + 13
?? <NSThread: 0x103007940>{number = 2, name = (null)} + 8
?? <NSThread: 0x103103480>{number = 1, name = main} + 14
?? <NSThread: 0x103007940>{number = 2, name = (null)} + 9
?? <NSThread: 0x103103480>{number = 1, name = main} + 15
?? <NSThread: 0x103007940>{number = 2, name = (null)} + 10
Program ended with exit code: 0

我們可以看到,??代表的任務(wù)全部是優(yōu)先執(zhí)行的,這說明sync添加的任務(wù)會阻塞當(dāng)前線程,在看到??和??是均勻分部的,這是由于async添加的任務(wù)會默認(rèn)加入由系統(tǒng)管理的線程池,異步執(zhí)行 。

優(yōu)先級 QoS


當(dāng)多個(gè)隊(duì)列同時(shí)執(zhí)行的時(shí)候,系統(tǒng)需要知道哪個(gè)隊(duì)列優(yōu)先級更高,才能優(yōu)先安排計(jì)算資源給他,我們可以這樣定義優(yōu)先級:

let q = DispatchQueue(label: "com.felix.felix", qos: DispatchQoS.background)

初始化的時(shí)候加上qos參數(shù) , qos(quality of service)從字面上理解就是「服務(wù)質(zhì)量」,在swift中是這樣定義的:

public enum QoSClass {

        @available(OSX 10.10, iOS 8.0, *)
        case background

        @available(OSX 10.10, iOS 8.0, *)
        case utility

        @available(OSX 10.10, iOS 8.0, *)
        case `default`

        @available(OSX 10.10, iOS 8.0, *)
        case userInitiated

        @available(OSX 10.10, iOS 8.0, *)
        case userInteractive

        case unspecified

        @available(OSX 10.10, iOS 8.0, *)
        public init?(rawValue: qos_class_t)

        @available(OSX 10.10, iOS 8.0, *)
        public var rawValue: qos_class_t { get }
    }


  • User Interactive: 和用戶交互相關(guān),比如動(dòng)畫等等優(yōu)先級最高。比如用戶連續(xù)拖拽的計(jì)算
  • User Initiated: 需要立刻的結(jié)果,比如push一個(gè)ViewController之前的數(shù)據(jù)計(jì)算
  • Utility: 可以執(zhí)行很長時(shí)間,再通知用戶結(jié)果。比如下載一個(gè)文件,給用戶下載進(jìn)度。
  • Background: 用戶不可見,比如在后臺存儲大量數(shù)據(jù)

在選擇優(yōu)先級時(shí)可以參考如下判斷 。

  • 這個(gè)任務(wù)是用戶可見的嗎?
  • 這個(gè)任務(wù)和用戶交互有關(guān)嗎?
  • 這個(gè)任務(wù)的執(zhí)行時(shí)間有多少?
  • 這個(gè)任務(wù)的最終結(jié)果和UI有關(guān)系嗎?

并發(fā)隊(duì)列


默認(rèn)情況下添加進(jìn)Queue的任務(wù)會串行執(zhí)行 , 先執(zhí)行完一個(gè)再執(zhí)行下一個(gè):

import Foundation

let q = DispatchQueue(label: "com.felix.felix")

q.async {
    (1...5).forEach({ i in
        print("?? \(Thread.current) + \(i)")
    })
}
q.async {
    (6...10).forEach({ i in
        print("?? \(Thread.current) + \(i)")
    })
}
(11...15).forEach({ i in
    print("?? \(Thread.current) + \(i)")
})

運(yùn)行看下日志輸出

?? <NSThread: 0x102a081a0>{number = 2, name = (null)} + 1
?? <NSThread: 0x100f046f0>{number = 1, name = main} + 11
?? <NSThread: 0x102a081a0>{number = 2, name = (null)} + 2
?? <NSThread: 0x100f046f0>{number = 1, name = main} + 12
?? <NSThread: 0x102a081a0>{number = 2, name = (null)} + 3
?? <NSThread: 0x100f046f0>{number = 1, name = main} + 13
?? <NSThread: 0x102a081a0>{number = 2, name = (null)} + 4
?? <NSThread: 0x100f046f0>{number = 1, name = main} + 14
?? <NSThread: 0x102a081a0>{number = 2, name = (null)} + 5
?? <NSThread: 0x100f046f0>{number = 1, name = main} + 15
?? <NSThread: 0x102a081a0>{number = 2, name = (null)} + 6
?? <NSThread: 0x102a081a0>{number = 2, name = (null)} + 7
?? <NSThread: 0x102a081a0>{number = 2, name = (null)} + 8
?? <NSThread: 0x102a081a0>{number = 2, name = (null)} + 9
Program ended with exit code: 0

我們可以看到直到??都輸出完畢才會輸出??,有時(shí)候我們想把任務(wù)并行執(zhí)行,怎么辦呢。
可以設(shè)置queue的Attributes。

let q = DispatchQueue(label: "com.felix.felix", attributes: DispatchQueue.Attributes.concurrent)

再運(yùn)行下看看會怎樣。

DispatchWorkItem


有的時(shí)候,對于同一個(gè)操作我們有可能會放在不同的線程中去執(zhí)行,這樣我們就可以把這個(gè)操作用DispatchWorkItem的形式包裹起來,在不同的線程中執(zhí)行 。

import Foundation

let group = DispatchGroup()

let q = DispatchQueue(label: "com.felix.felix", attributes: DispatchQueue.Attributes.concurrent)

let item1 = DispatchWorkItem {
    (1...5).forEach({ i in
        print("?? \(Thread.current) + \(i)")
    })
}

let item2 = DispatchWorkItem {
    (6...10).forEach({ i in
        print("?? \(Thread.current) + \(i)")
    })
}


q.async(execute: item1)

q.async(execute: item2)

(11...15).forEach({ i in
    print("?? \(Thread.current) + \(i)")
})

Group 隊(duì)列組

DispatchGroup 可以用來管理一組隊(duì)列,監(jiān)聽所有隊(duì)列的所有任務(wù)都完成的通知,比較常用的就是在一個(gè)頁面請求多個(gè)接口的時(shí)候,全部請求完再刷新UI 。

總結(jié)

總之,使用GCD一方面會提升我們應(yīng)用的性能,給用戶帶來更好的體驗(yàn),不過一定要注意線程安全問題。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 參考https://blog.csdn.net/Hello_Hwc/article/details/5429328...
    hwhjxjs閱讀 4,862評論 0 5
  • 從哪說起呢? 單純講多線程編程真的不知道從哪下嘴。。 不如我直接引用一個(gè)最簡單的問題,以這個(gè)作為切入點(diǎn)好了 在ma...
    Mr_Baymax閱讀 2,911評論 1 17
  • GCD簡介 GCD 是 libdispatch 的市場名稱,而 libdispatch 作為 Apple 的一個(gè)庫...
    獨(dú)木舟的木閱讀 1,410評論 0 5
  • 為什么要用GCD-Swift 當(dāng)今世界,多核已然普及。但是APP卻不見得很好的跟上了這個(gè)趨勢。APP 想要利用好多...
    uncle_charlie閱讀 1,012評論 0 3
  • GCD 一直以來是基于 c 語言的。apple 為了使 GCD 使用更加的 swift 化。 對 GCD 進(jìn)行了...
    Laughingg閱讀 18,515評論 19 91

友情鏈接更多精彩內(nèi)容