Swift 5.1 - GCD使用總結(jié)

在swift中GCD采用鏈?zhǔn)秸{(diào)用,較OC而言使用方式更為簡(jiǎn)單,可讀性更高。全文代碼均默認(rèn)在主線程中執(zhí)行。

隊(duì)列的獲取與創(chuàng)建

        //串行隊(duì)列
        let serial = DispatchQueue(label: "serial",attributes: .init(rawValue:0))
        //并發(fā)隊(duì)列
        let concurrent = DispatchQueue(label: "serial",attributes: .concurrent)
        //主隊(duì)列
        let mainQueue = DispatchQueue.main
        //全局隊(duì)列
        let global = DispatchQueue.global()

GCD隊(duì)列都遵循先進(jìn)先出(FIFO)。所以往并發(fā)隊(duì)列中添加同步任務(wù),其執(zhí)行順序和任務(wù)的添加順序相同。全局隊(duì)列在功能上和并發(fā)隊(duì)列是等價(jià)的,所以需要并發(fā)隊(duì)列時(shí),首選使用系統(tǒng)的全局隊(duì)列。

這里要注意一點(diǎn)并發(fā)隊(duì)列不能稱為并行隊(duì)列。請(qǐng)參考并發(fā)和并行的區(qū)別

同步任務(wù)與異步任務(wù)

主隊(duì)列+同步任務(wù)——死鎖

同步任務(wù)會(huì)阻塞線程,在如下代碼中需要優(yōu)先執(zhí)行print(2),等待其執(zhí)行后才能繼續(xù)往下,但是主隊(duì)列為串行隊(duì)列,需要等待當(dāng)前任務(wù)執(zhí)行完成后才能執(zhí)行后加入隊(duì)列的print(2)任務(wù),造成相互等待。

        print(1)
        DispatchQueue.main.sync {
            print(2)
        }
        print(3)
主隊(duì)列+異步任務(wù)——依次執(zhí)行(不開(kāi)啟新線程)

以下代碼中的print(2)任務(wù)會(huì)添加到主隊(duì)列的最后。又由于主線程+異步任務(wù)不會(huì)開(kāi)啟新線程,以下代碼輸出1 3 2,順序固定不變。

        print(1)
        DispatchQueue.main.async {
            print(2)
        }
        print(3)
串行隊(duì)列+同步任務(wù)——依次執(zhí)行

以下代碼不管每個(gè)任務(wù)休眠時(shí)間多長(zhǎng),輸出順序始終為0...10

        //串行隊(duì)列
        let serial = DispatchQueue(label: "serial",attributes: .init(rawValue:0))
        for i in 0...10 {
            serial.sync {
                sleep(arc4random()%3)//休眠時(shí)間隨機(jī)
                print(i)
            }
        }
串行隊(duì)列+異步任務(wù)——開(kāi)啟一個(gè)新線程依次執(zhí)行

以下代碼輸出順序始終為0...10,并且for循環(huán)中的Thread.current的輸出始終為同一個(gè)新線程

        //串行隊(duì)列
        let serial = DispatchQueue(label: "serial",attributes: .init(rawValue:0))
        print(Thread.current)//主線程
        for i in 0...10 {
            serial.async {
                sleep(arc4random()%3)//休眠時(shí)間隨機(jī)
                print(i,Thread.current)//子線程
            }
        }
并發(fā)隊(duì)列+同步任務(wù)——依次執(zhí)行

以下代碼輸出順序始終為0...10,且線程始終為主線程

        for i in 0...10 {
            DispatchQueue.global().sync {
                sleep(arc4random()%3)//休眠時(shí)間隨機(jī)
                print(i,Thread.current)
            }
        }
并發(fā)隊(duì)列+異步任務(wù)——開(kāi)啟多個(gè)線程并發(fā)執(zhí)行

以下代碼輸出順序隨機(jī),且線程信息不同。注意:這里可能不會(huì)輸出11個(gè)不同的線程信息,經(jīng)過(guò)代碼測(cè)試發(fā)現(xiàn)當(dāng)一個(gè)線程的任務(wù)執(zhí)行完成后,如果隊(duì)列中還有任務(wù),此線程會(huì)繼續(xù)被調(diào)度執(zhí)行后續(xù)任務(wù)。 將任務(wù)數(shù)增多,結(jié)果更明顯。

        for i in 0...10 {
            DispatchQueue.global().async {
                sleep(arc4random()%3)//休眠時(shí)間隨機(jī)
                print(i,Thread.current)
            }
        }
并發(fā)隊(duì)列——最大并發(fā)數(shù)

GCD并不能無(wú)限制的創(chuàng)建線程,如下代碼其實(shí)最多創(chuàng)建64個(gè)子線程,意味著最大并發(fā)數(shù)為64。參考

       for i in 0...1000 {
            DispatchQueue.global().async {
                print(i,Thread.current)
                sleep(10000)
            }
        }
GCD 柵欄

在swift中柵欄不再是一個(gè)單獨(dú)的方法。而是DispatchWorkItemFlags結(jié)構(gòu)體中的一個(gè)屬性。sync/async方法的其中一個(gè)參數(shù)類型即為DispatchWorkItemFlags,所以使用代碼如下。這樣的調(diào)用方式可以更好的理解柵欄,其實(shí)它就是一個(gè)分隔任務(wù),將其添加到需要柵欄的隊(duì)列中,以分隔添加前后的其他任務(wù)。以下代碼柵欄前后均為并發(fā)執(zhí)行。如果將添加?xùn)艡谛薷臑?code>sync則會(huì)阻塞當(dāng)前線程。

        for i in 0...10 {
            DispatchQueue.global().async {
                print(i)
            }
        }
        DispatchQueue.global().async(flags: .barrier) {
            print("this is barrier")
        }
        for i in 11...20 {
            DispatchQueue.global().async {
                print(i)
            }
        }
GCD group

隊(duì)列組一般用來(lái)處理任務(wù)的依賴,比如需要等待多個(gè)網(wǎng)絡(luò)請(qǐng)求返回后才能繼續(xù)執(zhí)行后續(xù)任務(wù)。

使用notify添加結(jié)束任務(wù)

必須要等待group中的任務(wù)執(zhí)行完成后才能執(zhí)行,無(wú)法定義超時(shí)。

    override func viewDidLoad() {
        let group = DispatchGroup()
        for i in 0...10 {
            DispatchQueue.global().async(group: group) {
                sleep(arc4random()%3)//休眠時(shí)間隨機(jī)
                print(i)
            }
        }
        //queue參數(shù)表示以下任務(wù)添加到的隊(duì)列
        group.notify(queue: DispatchQueue.main) {
            print("group 任務(wù)執(zhí)行結(jié)束")
        }
    }
使用wait進(jìn)行等待——可定義超時(shí)
        let group = DispatchGroup()
        for i in 0...10 {
            DispatchQueue.global().async(group: group) {
                sleep(arc4random()%10)//休眠時(shí)間隨機(jī)
                print(i)
            }
        }
        switch group.wait(timeout: DispatchTime.now()+5) {
        case .success:
            print("group 任務(wù)執(zhí)行結(jié)束")
        case .timedOut:
            print("group 任務(wù)執(zhí)行超時(shí)")
        }
enter()leave()

enter()是標(biāo)示一個(gè)任務(wù)加入到隊(duì)列,leave()標(biāo)識(shí)一個(gè)隊(duì)列中的任務(wù)執(zhí)行完成。
注意:enter()leave()必須成對(duì)調(diào)用
如果enter()后未調(diào)用leave()則不會(huì)觸發(fā)notify,wait也只會(huì)timeout。
如果leave()之前沒(méi)有調(diào)用enter()則會(huì)引起crash。

信號(hào)量 semaphore

GCD 中的信號(hào)量是指 Dispatch Semaphore,是持有計(jì)數(shù)的信號(hào)。類似于過(guò)高速路收費(fèi)站的欄桿。可以通過(guò)時(shí),打開(kāi)欄桿,不可以通過(guò)時(shí),關(guān)閉欄桿。在 Dispatch Semaphore 中,使用計(jì)數(shù)來(lái)完成這個(gè)功能,計(jì)數(shù)為0時(shí)等待,不可通過(guò)。計(jì)數(shù)為1或大于1時(shí),計(jì)數(shù)減1且不等待,可通過(guò)。

        let semaphore = DispatchSemaphore(value: 0)//創(chuàng)建一個(gè)信號(hào)量,并初始化信號(hào)總量
        semaphore.signal()//發(fā)送一個(gè)信號(hào)讓信號(hào)量加1
        semaphore.wait()//可以使總信號(hào)量減1,當(dāng)信號(hào)總量為0時(shí)就會(huì)一直等待(阻塞所在線程),否則就可以正常執(zhí)行。
信號(hào)量處理線程同步

將異步執(zhí)行任務(wù)轉(zhuǎn)化為同步執(zhí)行任務(wù)。如必須等待異步的網(wǎng)絡(luò)請(qǐng)求返回后才能執(zhí)行后續(xù)任務(wù)時(shí)。

        let semaphore = DispatchSemaphore(value: 0)
        DispatchQueue.global().async {
            sleep(arc4random()%5)//休眠時(shí)間隨機(jī)
            print("completed")
            semaphore.signal()
        }
        switch semaphore.wait(timeout: DispatchTime.now()+10) {//信號(hào)量為0,調(diào)用wait后阻塞線程
        case .success:
            print("success")
        case .timedOut:
            print("timeout")
        }
        print("over")
信號(hào)量控制最大并發(fā)數(shù)

在Operation中可以通過(guò)maxConcurrentOperationCount輕松實(shí)現(xiàn)控制最大并發(fā)數(shù),GCD中需要借助信號(hào)量實(shí)現(xiàn)。以下代碼就限制了最多兩個(gè)任務(wù)并發(fā)執(zhí)行。

        let semaphore = DispatchSemaphore(value: 2)
        for i in 0...10 {
            semaphore.wait()//當(dāng)信號(hào)量為0時(shí),阻塞在此
            DispatchQueue.global().async {
                sleep(3)
                print(i,Thread.current)
                semaphore.signal()//信號(hào)量加1
            }
            print("=======================")
        }
使用DispatchSemaphore加鎖

非線程安全,即當(dāng)一個(gè)變量可能同時(shí)被多個(gè)線程修改。以下代碼如果不使用信號(hào)量輸出是隨機(jī)值。

        let semaphore = DispatchSemaphore(value: 1)
        var i = 0
        for _ in 1...10 {
            DispatchQueue.global().async {
                semaphore.wait()//當(dāng)信號(hào)量為0時(shí),阻塞在此
                for _ in 1...10 {
                    i += 1
                }
                print(i)
                semaphore.signal()//信號(hào)量加1
            }
        }
延時(shí)任務(wù)

使用GCD執(zhí)行延時(shí)任務(wù)指定的并不是任務(wù)的執(zhí)行時(shí)間,而是加入隊(duì)列的時(shí)間。所以執(zhí)行時(shí)間可能不太精確。但是任務(wù)是通過(guò)閉包加入,相較performSelectorAfterDelay可讀性更好,也更安全。

        DispatchQueue.global().asyncAfter(deadline: DispatchTime.now()+2) {
            print("延時(shí)任務(wù)")
        }
dispatch_once

在swift3以后dispatch_once被廢棄。swift中有更好的定義單例和一次性代碼的方式。

DispatchWorkItem與任務(wù)取消

DispatchWorkItem其實(shí)就是用來(lái)代替OC中的dispatch_block_t。如果任務(wù)是通過(guò)DispatchWorkItem定義。在執(zhí)行之前,可以執(zhí)行取消操作。注意即使任務(wù)已經(jīng)加入隊(duì)列,只要還未執(zhí)行就可以進(jìn)行取消,但是無(wú)法判斷任務(wù)在隊(duì)列中的狀態(tài),所以一般會(huì)根據(jù)加入隊(duì)列的時(shí)間確定是否可以取消。

        let workItem = DispatchWorkItem {
            print("延時(shí)任務(wù)")
        }
        DispatchQueue.global().asyncAfter(deadline: DispatchTime.now()+2, execute: workItem)
        sleep(1)
        workItem.cancel()
DispatchWorkItem主動(dòng)執(zhí)行
        let workItem = DispatchWorkItem {
            print("workItem")
        }
        workItem.perform()
等待DispatchWorkItem執(zhí)行完成
        let workItem = DispatchWorkItem {
            sleep(3)
            print("workItem")
        }
        DispatchQueue.global().async(execute: workItem)
        switch workItem.wait(timeout: DispatchTime.now()+5) {
        case .success:
            print("success")
        case .timedOut:
            print("timeout")
        }
DispatchWorkItem執(zhí)行完成通知
        let workItem = DispatchWorkItem {
            sleep(3)
            print("workItem")
        }
        DispatchQueue.global().async(execute: workItem)
        workItem.notify(queue: DispatchQueue.main) {
            print("completed")
        }

參考:

GCD最大線程數(shù)

iOS 多線程:『GCD』詳盡總結(jié)

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

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