iOS 多線程 GCD (Swift 3)

0. 前言

GCD(Grand Central Dispatch)是 Apple 開(kāi)發(fā)的一種多核編程的方法,主要的作用是優(yōu)化應(yīng)用程序?qū)Χ嗪颂幚砥鞯倪\(yùn)用,是在線程池模式的基礎(chǔ)上執(zhí)行的并行任務(wù)。GCD中有兩個(gè)重要的基本概念——任務(wù)隊(duì)列。


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

任務(wù)

就是執(zhí)行的操作,也就是在線程中執(zhí)行的代碼,封裝在 Swift 的閉包(Closure)或者 OC 的塊(block)里。根據(jù)能否具備開(kāi)啟新線程可以分成 同步執(zhí)行異步執(zhí)行。

  • 同步執(zhí)行(sync) 只能在當(dāng)前線程中執(zhí)行任務(wù),不能開(kāi)新線程,當(dāng)前的代碼段沒(méi)有執(zhí)行完就不能夠執(zhí)行下一部分,會(huì)阻塞當(dāng)前線程。
  • 異步執(zhí)行(async) 可以在新線程中執(zhí)行任務(wù),能夠開(kāi)啟新線程,不用等待當(dāng)前的代碼段執(zhí)行完就可以往下執(zhí)行后續(xù)代碼,不會(huì)阻塞當(dāng)前線程。

隊(duì)列

就是任務(wù)隊(duì)列,是一種特殊的線性表,滿足 FIFO 的原則。在 GCD 里分為 串行隊(duì)列并發(fā)隊(duì)列。

  • 串行隊(duì)列(Serial Dispatch Queue) 一個(gè)任務(wù)完成后接著執(zhí)行下一個(gè)任務(wù)。
  • 并發(fā)隊(duì)列(Concurrent Dispatch Queue) 自動(dòng)開(kāi)啟多個(gè)線程進(jìn)行多個(gè)任務(wù)并發(fā)執(zhí)行,而且并發(fā)只有在異步執(zhí)行(dispatch_async)情況下才有效。

2. 創(chuàng)建隊(duì)列

系統(tǒng)默認(rèn)隊(duì)列——主隊(duì)列(串行)

//主隊(duì)列
let mainQueue = DispatchQueue.main

系統(tǒng)默認(rèn)隊(duì)列——全局隊(duì)列(并發(fā))

//全局隊(duì)列
let globalQueue = DispatchQueue.global()

全局隊(duì)列有 4 個(gè)執(zhí)行的優(yōu)先級(jí),從高到低分別是:
.userInitiated,.default,.utility,.background
所以當(dāng)需要?jiǎng)?chuàng)建指定某個(gè)優(yōu)先級(jí)的全局隊(duì)列時(shí):

let defaultGlobalQueue = DispatchQueue.global(qos: .default)

自定義創(chuàng)建隊(duì)列

//創(chuàng)建串行隊(duì)列
let serialQueue = DispatchQueue(label: "serialQueue_1")
//創(chuàng)建優(yōu)先級(jí)為default的串行隊(duì)列
let serialQueue = DispatchQueue(label: "serialQueue_2", qos: .default)

//創(chuàng)建優(yōu)先級(jí)為background的并發(fā)隊(duì)列
let concurrentQueue = DispatchQueue(label: "concurrentQueue", qos: .background, attributes: .concurrent)

隊(duì)列執(zhí)行

隊(duì)列的執(zhí)行部分可以用 3 個(gè)任務(wù)來(lái)反映出任務(wù)執(zhí)行的順序。

同步并行

代碼示例:

func syncConcurrent() {

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

  print("--- begin ---")

  queue.sync {
      for i in 1...3 {
          print("--- ??? \(i) ---\(Thread.current)")
      }
  }

  queue.sync {
      for i in 1...3 {
          print("--- ??? \(i) ---\(Thread.current)")
      }
  }

  queue.sync {
      for i in 1...3 {
          print("--- ??? \(i) ---\(Thread.current)")
      }
  }

  print("--- end ---")

}

打印結(jié)果

--- begin ---
--- ??? 1 ---<NSThread: 0x60400006f380>{number = 1, name = main}
--- ??? 2 ---<NSThread: 0x60400006f380>{number = 1, name = main}
--- ??? 3 ---<NSThread: 0x60400006f380>{number = 1, name = main}
--- ??? 1 ---<NSThread: 0x60400006f380>{number = 1, name = main}
--- ??? 2 ---<NSThread: 0x60400006f380>{number = 1, name = main}
--- ??? 3 ---<NSThread: 0x60400006f380>{number = 1, name = main}
--- ??? 1 ---<NSThread: 0x60400006f380>{number = 1, name = main}
--- ??? 2 ---<NSThread: 0x60400006f380>{number = 1, name = main}
--- ??? 3 ---<NSThread: 0x60400006f380>{number = 1, name = main}
--- end ---

任務(wù)是按照順序逐個(gè)執(zhí)行的,并且只有一個(gè)主線程在戰(zhàn)斗

異步并行

代碼示例

func asyncConcurrent() {

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

    print("--- begin ---")

    queue.async {
        for i in 1...3 {
            print("--- ??? \(i) ---\(Thread.current)")
        }
    }

    queue.async {
        for i in 1...3 {
            print("--- ??? \(i) ---\(Thread.current)")
        }
    }

    queue.async {
        for i in 1...3 {
            print("--- ??? \(i) ---\(Thread.current)")
        }
    }

    print("--- end ---")

}

打印結(jié)果 1

--- begin ---
--- ??? 1 ---<NSThread: 0x604000078bc0>{number = 3, name = (null)}
--- ??? 1 ---<NSThread: 0x6080000747c0>{number = 4, name = (null)}
--- ??? 2 ---<NSThread: 0x604000078bc0>{number = 3, name = (null)}
--- ??? 1 ---<NSThread: 0x6040000793c0>{number = 5, name = (null)}
--- ??? 2 ---<NSThread: 0x6080000747c0>{number = 4, name = (null)}
--- ??? 3 ---<NSThread: 0x604000078bc0>{number = 3, name = (null)}
--- end ---
--- ??? 2 ---<NSThread: 0x6040000793c0>{number = 5, name = (null)}
--- ??? 3 ---<NSThread: 0x6080000747c0>{number = 4, name = (null)}
--- ??? 3 ---<NSThread: 0x6040000793c0>{number = 5, name = (null)}

打印結(jié)果 2

--- begin ---
--- ??? 1 ---<NSThread: 0x604000069140>{number = 3, name = (null)}
--- ??? 1 ---<NSThread: 0x60800006aa40>{number = 4, name = (null)}
--- ??? 2 ---<NSThread: 0x604000069140>{number = 3, name = (null)}
--- ??? 2 ---<NSThread: 0x60800006aa40>{number = 4, name = (null)}
--- ??? 1 ---<NSThread: 0x60800006a900>{number = 5, name = (null)}
--- ??? 3 ---<NSThread: 0x604000069140>{number = 3, name = (null)}
--- end ---
--- ??? 2 ---<NSThread: 0x60800006a900>{number = 5, name = (null)}
--- ??? 3 ---<NSThread: 0x60800006aa40>{number = 4, name = (null)}
--- ??? 3 ---<NSThread: 0x60800006a900>{number = 5, name = (null)}

每一次運(yùn)行的執(zhí)行順序不一定相同,任務(wù)的執(zhí)行是交替的,一共新開(kāi)了 3 個(gè)線程在戰(zhàn)斗。并且從 begin 和 end 的位置可以看出,3 個(gè)任務(wù)被添加到隊(duì)列之后就立刻開(kāi)始了戰(zhàn)斗。

同步串行

func syncSerial() {

    let queue = DispatchQueue(label: "queue")

    print("--- begin ---")

    queue.sync {
        for i in 1...3 {
            print("--- ??? \(i) ---\(Thread.current)")
        }
    }

    queue.sync {
        for i in 1...3 {
            print("--- ??? \(i) ---\(Thread.current)")
        }
    }

    queue.sync {
        for i in 1...3 {
            print("--- ??? \(i) ---\(Thread.current)")
        }
    }

    print("--- end ---")

}

打印結(jié)果
--- begin ---
--- ??? 1 ---<NSThread: 0x6000000777c0>{number = 1, name = main}
--- ??? 2 ---<NSThread: 0x6000000777c0>{number = 1, name = main}
--- ??? 3 ---<NSThread: 0x6000000777c0>{number = 1, name = main}
--- ??? 1 ---<NSThread: 0x6000000777c0>{number = 1, name = main}
--- ??? 2 ---<NSThread: 0x6000000777c0>{number = 1, name = main}
--- ??? 3 ---<NSThread: 0x6000000777c0>{number = 1, name = main}
--- ??? 1 ---<NSThread: 0x6000000777c0>{number = 1, name = main}
--- ??? 2 ---<NSThread: 0x6000000777c0>{number = 1, name = main}
--- ??? 3 ---<NSThread: 0x6000000777c0>{number = 1, name = main}
--- end ---

打印結(jié)果和同步并行一個(gè)樣,都是只有主線程在戰(zhàn)斗,并且按照順序逐一執(zhí)行。

串行異步

代碼示例

func asyncSerial() {

    let queue = DispatchQueue(label: "queue")

    print("--- begin ---")

    queue.async {
        for i in 1...3 {
            print("--- ??? \(i) ---\(Thread.current)")
        }
    }

    queue.async {
        for i in 1...3 {
            print("--- ??? \(i) ---\(Thread.current)")
        }
    }

    queue.async {
        for i in 1...3 {
            print("--- ??? \(i) ---\(Thread.current)")
        }
    }

    print("--- end ---")

}

打印結(jié)果

--- begin ---
--- ??? 1 ---<NSThread: 0x60400007a700>{number = 3, name = (null)}
--- ??? 2 ---<NSThread: 0x60400007a700>{number = 3, name = (null)}
--- ??? 3 ---<NSThread: 0x60400007a700>{number = 3, name = (null)}
--- end ---
--- ??? 1 ---<NSThread: 0x60400007a700>{number = 3, name = (null)}
--- ??? 2 ---<NSThread: 0x60400007a700>{number = 3, name = (null)}
--- ??? 3 ---<NSThread: 0x60400007a700>{number = 3, name = (null)}
--- ??? 1 ---<NSThread: 0x60400007a700>{number = 3, name = (null)}
--- ??? 2 ---<NSThread: 0x60400007a700>{number = 3, name = (null)}
--- ??? 3 ---<NSThread: 0x60400007a700>{number = 3, name = (null)}

任務(wù)是按順序逐一進(jìn)行的,開(kāi)啟了一條新的線程。

主隊(duì)列和全局隊(duì)列的同步/異步

同步

DispatchQueue.global(qos: .default).async {
    print("I'm in global.")
    DispatchQueue.main.async {
        print("I'm back in main.")
    }
}

異步

//Global Dispatch Queue
//不會(huì)死鎖,但代碼順序執(zhí)行,后續(xù)代碼需要等待前面代碼執(zhí)行完畢
DispatchQueue.global(qos: .default).sync {
    print("I'm in global.")
    print("I'm still in global.")
}
print("I'm out.")

//Main Queue
//死鎖。按照同步的執(zhí)行順序,一個(gè)任務(wù)需要等待前一個(gè)任務(wù)執(zhí)行完畢,但是"I'm in main."是添加在"I'm out."之后,但是"I'm out"又需要等待"I'm in main."執(zhí)行完畢之后再執(zhí)行。所以彼此等待造成了死鎖。
DispatchQueue.main.sync {
    print("I'm in main.")
}
print("I'm out.")

隊(duì)列暫停和繼續(xù)

//暫停
queue.suspend()
//繼續(xù)
queue.resume()

suspend() 和 resume() 是異步函數(shù),在兩個(gè)閉包之間生效。
suspend() 會(huì)使得已經(jīng)添加到 Dispatch Queue 但是還沒(méi)有執(zhí)行的任務(wù)在改行代碼之后暫停執(zhí)行,等到 resume() 之后才能繼續(xù)執(zhí)行。

單次執(zhí)行

單次執(zhí)行在多線程編程的使用過(guò)程中很常見(jiàn),當(dāng)然除了多線程之外也有一些其他的用途。
在 Swift 3 之前,單次執(zhí)行可以通過(guò) dispatch_once 來(lái)實(shí)現(xiàn),但是 Swift 3 把它廢棄了... 查了一些資料發(fā)現(xiàn)要實(shí)現(xiàn)原來(lái)的 dispatch_once 可以通過(guò)下面幾個(gè)方式:

  1. 全局常量
let constant = SomeClass()
  1. 全局變量(帶有立即執(zhí)行的閉包構(gòu)造器)
var variable: SomeClass = {
    let constant = SomeClass()
    constant.oneProperty = "HeySiri"
    constant.oneMethod()
    return constant
}()

3. 未完待續(xù)...

最后編輯于
?著作權(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)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

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

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