iOS中的多線程技術(shù)

多線程

多線程技術(shù)是iOS開發(fā)里十分常見的,下面會(huì)介紹GCD的常用多線程技術(shù)。首先簡(jiǎn)單了解一下幾個(gè)概念:

  • 同步,異步:任務(wù)執(zhí)行時(shí)是否開多線程,同步為單線程,異步為多線程
  • 串行,并發(fā):任務(wù)執(zhí)行的方式,串行為順序執(zhí)行,并發(fā)為同時(shí)執(zhí)行
  • 隊(duì)列:任務(wù)存放的地方,線程從隊(duì)列里取出任務(wù)執(zhí)行。分為串行隊(duì)列和并發(fā)隊(duì)列
  • 線程:執(zhí)行任務(wù)的實(shí)際運(yùn)作單位,主線程從主隊(duì)列里取任務(wù),更新UI必須在主線程完成

GCD:

GCD是操作最簡(jiǎn)單,也是我們最常用的多線程技術(shù)。在OC里是一套C語言API,在Swift里對(duì)其封裝進(jìn)一步簡(jiǎn)化,本文會(huì)使用Swift語法。

一。主隊(duì)列,全局隊(duì)列

先來看看這個(gè)GCD的最常用公式:

DispatchQueue.global().async {   
    //long time operation
    DispatchQueue.main.async {   
        //update UI
    }
}

它分為以下幾個(gè)步驟:

  • 通過DispatchQueue.global()獲取全局隊(duì)列
  • 在全局隊(duì)列中async并發(fā)執(zhí)行耗時(shí)操作,如下載
  • 通過DispatchQueue.main獲取全局隊(duì)列
  • 在全局隊(duì)列中async并發(fā)執(zhí)行更新UI的動(dòng)作

那么為什么要如此使用呢?

  • 首先,更新UI的操作必須放在主線程中,否則會(huì)報(bào)錯(cuò)。這里DispatchQueue.main是獲取主隊(duì)列,它是一個(gè)串行隊(duì)列,而主線程要執(zhí)行的任務(wù)就是到這個(gè)隊(duì)列里去取的。
  • DispatchQueue.global()是獲取系統(tǒng)提供的全局隊(duì)列,該隊(duì)列是并發(fā)隊(duì)列。加入該隊(duì)列的任務(wù)會(huì)開辟多條線程共同執(zhí)行,就好像一個(gè)頁面會(huì)下載顯示很多張圖片,用全局隊(duì)列的話圖片會(huì)一張張顯示出來,而如果用主隊(duì)列的話,所有圖片下載完成才統(tǒng)一顯示。
  • 這里不管是主隊(duì)列還是全局隊(duì)列都使用async,而沒有使用sync是因?yàn)?code>sync同步操作,相當(dāng)于將所有操作都由主線程來執(zhí)行,這會(huì)阻塞主線程,也就是UI上的卡頓。

這里寫了一個(gè)小實(shí)驗(yàn)比較上面的各種情況。(因?yàn)閳D片地址在公司服務(wù)器上,這里不公布代碼,只看結(jié)果)。

點(diǎn)擊一個(gè)button,會(huì)Push到下一個(gè)頁面,而下一個(gè)頁面中會(huì)有一個(gè)CollectionView,共包含10個(gè)CollectionViewCell,每一個(gè)cell中都會(huì)下載并顯示一張圖片。

  • 第一張圖用的是之前的標(biāo)準(zhǔn)公式,點(diǎn)擊按鈕后立即Push到下一個(gè)頁面,并且圖片也相繼顯示出來。

    GCDDemo1.gif
  • 第二張圖是將DispatchQueue.global()換成了DispatchQueue.main()??梢钥吹?,點(diǎn)擊按鈕后立即Push到下一個(gè)頁面,所有圖片下載完成后再一起顯示,這是因?yàn)橹麝?duì)列是串行隊(duì)列,所有的更新UI任務(wù)都排在了下載任務(wù)之后,因此要等所有的圖片下載完成后,UI才會(huì)開始顯示。

    [圖片上傳中...(GCDDemo3.gif-83a055-1564041778608-0)]
  • 第三張圖是將DispatchQueue.global().async換成了DispatchQueue.global().sync??梢钥吹剑c(diǎn)擊按鈕后卡頓了一會(huì)兒,再進(jìn)入下一個(gè)頁面。這是因?yàn)?code>sync是同步操作,會(huì)阻塞主線程。

    GCDDemo3.gif

由以上三張圖可看出,常用公式是最合適的選擇


二。創(chuàng)建隊(duì)列

let queue = DispatchQueue(label: "com.demo.queue", qos: .background, attributes: .concurrent)
  • qos是指隊(duì)列的優(yōu)先級(jí),從background到userinteractive,優(yōu)先級(jí)從低到高
  • concurrent指定了該隊(duì)列為并發(fā)隊(duì)列,若不穿,則默認(rèn)為串行隊(duì)列

自建的隊(duì)列最終會(huì)被歸入到主隊(duì)列和全局隊(duì)列中去,那么為什么還要?jiǎng)?chuàng)建它們呢,也是便于對(duì)一系列任務(wù)的管理。


三。派發(fā)組DispatchGroup

開發(fā)中也許會(huì)有這一種情況,我們要同時(shí)請(qǐng)求好幾個(gè)接口后,再刷新UI。這種對(duì)多個(gè)異步請(qǐng)求的管理,用DispatchGroup最合適。

用法如下

let group = DispatchGroup()

group.enter()
//task1
group.leave()

group.enter()
//task2
group.leave()

group.enter()
//task3
group.leave()

group.notify(queue: DispatchQueue.main) {
    //update UI
    print("更新UI")
}

更新UI的操作會(huì)在task1,task2,task3這3個(gè)異步任務(wù)完成后再執(zhí)行。


四。信號(hào)量semaphore

簡(jiǎn)單的說,在異步操作中,任務(wù)完成的順序是不確定的。semaphore可以使得我們將異步操作按順序同步完成。

這里有一片博客,對(duì)其進(jìn)行了詳細(xì)的介紹


五。屏障barrier

想象有這樣一個(gè)操作。從數(shù)據(jù)庫里執(zhí)行兩次讀的任務(wù)read1和read2,并發(fā)執(zhí)行并沒有什么問題??扇绻趓ead1和read2中間加入一個(gè)write1,要求read1讀取的是write之前的數(shù)據(jù),read2讀取的是write之后的數(shù)據(jù),那么應(yīng)該如何處理呢?

首先,如果write不能用普通的并發(fā)操作。因?yàn)椴l(fā)隊(duì)列的特性是無法確保read1,read2以及write的執(zhí)行順序,這可能會(huì)發(fā)生read2讀取的是write之前的數(shù)據(jù)。

這里我們需要用到barrier。顧名思義,它是作為一個(gè)屏障,隔開了read1和read2,在這個(gè)屏障內(nèi)的進(jìn)行write,也會(huì)保證,read1->write->read2這樣的一個(gè)執(zhí)行順序

DispatchQueue.global().async {
    //read1
}
DispatchQueue.global().async(flags: .barrier) {
    //write
}
DispatchQueue.global().async {
    //read2
}

六。延遲執(zhí)行

這里用GCD封裝一個(gè)延遲執(zhí)行

func delay(_ timeInterval: TimeInterval, closure: @escaping ()->()) {
    DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + timeInterval, execute: closure)
}

調(diào)用起來非常簡(jiǎn)單和明了

delay(3) {
        //task  
}

值的一體的是,該延遲執(zhí)行指的并不是在3秒后執(zhí)行task,而指的是,在3秒后,將task加入主隊(duì)列。所以當(dāng)主隊(duì)列有大量處理或者本身已經(jīng)延遲的情況下,真正執(zhí)行的時(shí)間會(huì)比3秒更長(zhǎng)。不過一般來說,在主隊(duì)列非阻塞的情況下,該方法還是算非常簡(jiǎn)潔有效的。


七。創(chuàng)建單例

得益于GCD中的dispatch_once,這個(gè)函數(shù)會(huì)讓程序只執(zhí)行一次,我們?cè)贠C中用它來實(shí)現(xiàn)單例。

static dispatch_once_t pred;
dispatch_once(pred, ^{
    //init
});

不過為了防止單例類通過alloc或者new的方法實(shí)例化,實(shí)際上要寫的代碼還有很多,相比較下來Swift的單例實(shí)現(xiàn)就要簡(jiǎn)單很多。

class Singleton {
    static let shared = Singleton()
    private init() {}
}

八。死鎖

死鎖一般是指同步任務(wù)的相互等待,造成程序崩潰。

例如,我們?cè)赩iewController的ViewDidLoad里加入這一句,程序執(zhí)行到這里立刻就會(huì)崩潰。

DispatchQueue.main.sync {}

因?yàn)閂iewDidLoad是從主線程執(zhí)行,而DispatchQueue.main.sync {}也是在主線程里同步執(zhí)行。ViewDidLoad需要等待DispatchQueue.main.sync {}完成,但ViewDidLoad又是先加入主隊(duì)列的,因此DispatchQueue.main.sync {}要執(zhí)行必須先等ViewDidLoad執(zhí)行完畢。因此他們相互等待,造成死鎖。

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

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

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