GCD

基本概念

什么是 GCD 抄罕?

官方文檔 說明如下:
Grand Central Dispatch( GCD )是異步啟動任務(wù)的技術(shù)之一耐齐。此技術(shù)將開發(fā)者通常在應(yīng)用程序中編寫的線程管理代碼在系統(tǒng)級別中實(shí)現(xiàn)。開發(fā)者通常要做的是定義要執(zhí)行的任務(wù)并將它們添加到適當(dāng)?shù)?Dispatch Queue 中。GCD 就能創(chuàng)建所需的線程并計劃執(zhí)行任務(wù)催享。由于線程管理現(xiàn)在是系統(tǒng)的一部分裆操,因此可以統(tǒng)一管理,也可執(zhí)行任務(wù),這樣就比傳統(tǒng)線程更有效率华临。

為什么要用 GCD ?
  • GCD 會自動管理線程的生命周期,無需開發(fā)者編寫任何線程管理代碼
  • GCD 可以通過將昂貴的計算任務(wù)放入后臺處理,來改善應(yīng)用的響應(yīng)性能
  • GCD 提供一個易于使用的并發(fā)模型而不僅僅只是鎖和線程,以幫助我們避開并發(fā)陷阱
  • GCD 具有在常見模式(例如單例)上用更高性能的原語優(yōu)化你的代碼的潛在能力

任務(wù)與隊(duì)列

任務(wù)

任務(wù)是指需要執(zhí)行的操作褒搔,在 GCD 中就是 block 中的那一段代碼。執(zhí)行任務(wù)分為同步(sync) 和異步(async):

  • 同步(sync)
    • 同步添加任務(wù)到指定隊(duì)列中翎承,會一直等待任務(wù)執(zhí)行結(jié)束,再執(zhí)行后續(xù)程序
    • 會阻塞線程
    • 不會開啟新線程
  • 異步(async)
    • 異步添加任務(wù)到指定隊(duì)列中箱锐,不用等待任務(wù)執(zhí)行結(jié)束,就可以立即返回執(zhí)行后續(xù)程序
    • 不會阻塞線程
    • 可能會開啟新線程
隊(duì)列(DispatchQueue)

隊(duì)列是一種特殊的線性表臊恋,用于存放任務(wù)衣洁,采用 FIFO(先進(jìn)先出)的原則。在 GCD 中隊(duì)列主要分為串行隊(duì)列(Serial Dispatch Queue)并行隊(duì)列(Concurrent Dispatch Queue)抖仅,兩者都符合 FIFO 的原則坊夫,主要區(qū)別是執(zhí)行的順序,以及開啟的線程數(shù)不同撤卢。

  • 串行隊(duì)列(Serial Dispatch Queue):使用一個線程环凿,一個接著一個執(zhí)行任務(wù)
  • 并行隊(duì)列(Concurrent Dispatch Queue):使用多個線程,同時執(zhí)行多個任務(wù)
    隊(duì)列

隊(duì)列與線程

主隊(duì)列
DispatchQueue(label: "queue").async {
    print("queue: \(Thread.current)")
    DispatchQueue.main.sync {
        print("mainSync: \(Thread.current)")
    }
    DispatchQueue.main.async {
        print("mainAsync1: \(Thread.current)")
    }
    DispatchQueue.main.async {
        print("mainAsync2: \(Thread.current)")
    }
}

打油雇琛:
queue: <NSThread: 0x6000031c1580>{number = 3, name = (null)}
mainSync: <NSThread: 0x6000031920c0>{number = 1, name = main}
mainAsync1: <NSThread: 0x6000031920c0>{number = 1, name = main}
mainAsync2: <NSThread: 0x6000031920c0>{number = 1, name = main}

可以看出主隊(duì)列不管同步還是異步執(zhí)行任務(wù)都是在主線程中的拷邢。

串行隊(duì)列
DispatchQueue(label: "queue").async {
    print("queue: \(Thread.current)")
    let serialQueue = DispatchQueue(label: "serial")
    serialQueue.sync {
        print("serialSync1: \(Thread.current)")
    }
    serialQueue.sync {
        print("serialSync2: \(Thread.current)")
    }
    serialQueue.async {
        print("serialAsync1: \(Thread.current)")
    }
    serialQueue.async {
        print("serialAsync2: \(Thread.current)")
    }
}
打印1:
queue: <NSThread: 0x6000038128c0>{number = 3, name = (null)}
serialSync1: <NSThread: 0x6000038128c0>{number = 3, name = (null)}
serialSync2: <NSThread: 0x6000038128c0>{number = 3, name = (null)}
serialAsync1: <NSThread: 0x6000038128c0>{number = 3, name = (null)}
serialAsync2: <NSThread: 0x6000038128c0>{number = 3, name = (null)}
打印2:
queue: <NSThread: 0x600001758fc0>{number = 3, name = (null)}
serialSync1: <NSThread: 0x600001758fc0>{number = 3, name = (null)}
serialSync2: <NSThread: 0x600001758fc0>{number = 3, name = (null)}
serialAsync1: <NSThread: 0x600001762700>{number = 4, name = (null)}
serialAsync2: <NSThread: 0x600001762700>{number = 4, name = (null)}

可以看出串行隊(duì)列同步執(zhí)行任務(wù)的時候是在當(dāng)前線程,而異步執(zhí)行任務(wù)的時候可能在當(dāng)前線程屎慢,也可能是新開一條線程來處理。

DispatchQueue(label: "queue").async {
    print("queue: \(Thread.current)")
    let serialQueue = DispatchQueue(label: "serial")
    serialQueue.async {
        Thread.sleep(forTimeInterval: 2)
        print("serialAsync2: \(Thread.current)")
    }
    print("xx")
    serialQueue.sync {
        print("serialSync1: \(Thread.current)")
    }
    print("yy")
}
打雍雎濉:
queue: <NSThread: 0x6000000714c0>{number = 3, name = (null)}
xx
2秒后:
serialAsync2: <NSThread: 0x600000051e80>{number = 4, name = (null)}
serialSync1: <NSThread: 0x6000000714c0>{number = 3, name = (null)}
yy

可以看出在串行隊(duì)列中異步任務(wù)雖然立即返回了腻惠,但當(dāng)后續(xù)還有其他任務(wù)時,依然要等待異步任務(wù)執(zhí)行完成后才能執(zhí)行欲虚,造成線程阻塞集灌。

并行隊(duì)列
DispatchQueue(label: "queue").async {
    print("queue: \(Thread.current)")
    let concurrentQueue = DispatchQueue(label: "concurrent", qos: .default, attributes: .concurrent)
    concurrentQueue.sync {
        print("concurrentSync1: \(Thread.current)")
    }
    concurrentQueue.sync {
        print("concurrentSync2: \(Thread.current)")
    }
    concurrentQueue.async {
        print("concurrentAsync1: \(Thread.current)")
    }
    concurrentQueue.async {
        print("concurrentAsync2: \(Thread.current)")
    }
}

打印:
queue: <NSThread: 0x600001b76540>{number = 3, name = (null)}
concurrentSync1: <NSThread: 0x600001b76540>{number = 3, name = (null)}
concurrentSync2: <NSThread: 0x600001b76540>{number = 3, name = (null)}
concurrentAsync1: <NSThread: 0x600001b76540>{number = 3, name = (null)}
concurrentAsync2: <NSThread: 0x600001b76540>{number = 3, name = (null)}

可以看出并行隊(duì)列同步執(zhí)行任務(wù)時候是在當(dāng)前線程复哆,異步任務(wù)也可能是在當(dāng)前線程欣喧。

DispatchQueue(label: "queue").async {
    print("queue: \(Thread.current)")
    let concurrentQueue = DispatchQueue(label: "concurrent", qos: .default, attributes: .concurrent)
    concurrentQueue.sync {
        Thread.sleep(forTimeInterval: 2)
        print("concurrentSync1: \(Thread.current)")
    }
    print("xx")
    concurrentQueue.async {
        Thread.sleep(forTimeInterval: 2)
        print("concurrentAsync1: \(Thread.current)")
    }
    print("yy")
    concurrentQueue.async {
        Thread.sleep(forTimeInterval: 2)
        print("concurrentAsync2: \(Thread.current)")
    }
    print("zz")
    concurrentQueue.sync {
        print("concurrentSync2: \(Thread.current)")
    }
}

打印:
queue: <NSThread: 0x60000311f6c0>{number = 3, name = (null)}
2秒后:
concurrentSync1: <NSThread: 0x60000311f6c0>{number = 3, name = (null)}
xx
yy
zz
concurrentSync2: <NSThread: 0x60000311f6c0>{number = 3, name = (null)}
4秒后:
concurrentAsync2: <NSThread: 0x60000310ae40>{number = 4, name = (null)}
concurrentAsync1: <NSThread: 0x60000311f6c0>{number = 3, name = (null)}

可以看出并行隊(duì)列同步執(zhí)行任務(wù)的時候也會阻塞當(dāng)前線程梯找,異步執(zhí)行任務(wù)則會在當(dāng)前線程或者新開線程執(zhí)行而且不會阻塞線程唆阿。

總結(jié)
  • 主隊(duì)列的任務(wù)都在主線程執(zhí)行,在主線程上調(diào)用主隊(duì)列同步執(zhí)行任務(wù)會造成死鎖
  • 同步執(zhí)行任務(wù)都在當(dāng)前線程
  • 異步執(zhí)行任務(wù)也可能會在當(dāng)前線程锈锤,也可能新開線程(串行隊(duì)列會新開一條驯鳖,并行隊(duì)列會新開多條)
  • 串行隊(duì)列執(zhí)行任務(wù)都是一個接一個執(zhí)行的,就算是異步任務(wù)久免,也只是提前返回浅辙,后續(xù)任務(wù)也要等該異步任務(wù)執(zhí)行完成后才能繼續(xù)
  • 并行隊(duì)列異步執(zhí)行任務(wù)的時候是開啟多個線程并發(fā)執(zhí)行多個任務(wù),不會造成線程阻塞
主隊(duì)列(main) 串行隊(duì)列(serial) 并行隊(duì)列(concurrent)
同步(sync) 主線程 當(dāng)前線程 當(dāng)前線程
異步(async) 主線程 當(dāng)前線程/新線程(一個) 當(dāng)前線程/新線程(多個)

關(guān)于同步執(zhí)行任務(wù)都在當(dāng)前線程阎姥,官方文檔 描述如下:

As a performance optimization, this function executes blocks on the current thread whenever possible, with one obvious exception. Specifically, blocks submitted to the main dispatch queue always run on the main thread.

其他多線程編程技術(shù)

GCD 的幾種操作

Group

打包幾個異步任務(wù)记舆,等待它們都執(zhí)行完之后發(fā)出通知。

func group() {
    let group = DispatchGroup()
    let queue = DispatchQueue(label: "groupQueue", qos: .default, attributes: .concurrent)

    queue.async(group: group) {
        Thread.sleep(forTimeInterval: 2)
        print("1111")
    }

    queue.async(group: group) {
        Thread.sleep(forTimeInterval: 5)
        print("2222")
    }

    group.notify(queue: queue) {
        print("over")
    }

    print("xxxx")
}

打雍舭汀:
xxxx
1111
2222
over
barrier

柵欄任務(wù)的主要特性是可以對隊(duì)列中的任務(wù)進(jìn)行阻隔泽腮,執(zhí)行柵欄任務(wù)時御蒲,它會先等待隊(duì)列中已有的任務(wù)全部執(zhí)行完成,然后它再執(zhí)行盛正,在它之后加入的任務(wù)也必須等柵欄任務(wù)執(zhí)行完后才能執(zhí)行删咱。

func barrier() {
    let concurrentQueue = DispatchQueue(label: "concurrent", qos: .default, attributes: .concurrent)
    concurrentQueue.async {
        Thread.sleep(forTimeInterval: 2)
        print("concurrentAsync1: \(Thread.current)")
    }
    concurrentQueue.async {
        Thread.sleep(forTimeInterval: 2)
        print("concurrentAsync2: \(Thread.current)")
    }

    concurrentQueue.async(flags: .barrier) {
        Thread.sleep(forTimeInterval: 4)
        print("柵欄")
        print("barTask: \(Thread.current)")
    }

    concurrentQueue.async {
        Thread.sleep(forTimeInterval: 2)
        print("concurrentAsync3: \(Thread.current)")
    }
    concurrentQueue.async {
        Thread.sleep(forTimeInterval: 2)
        print("concurrentAsync4: \(Thread.current)")
    }
}

//另一種寫法
func barrier() {
    let barTask = DispatchWorkItem(flags: .barrier) {
        Thread.sleep(forTimeInterval: 4)
        print("柵欄")
        print("barTask: \(Thread.current)")
    }

    let concurrentQueue = DispatchQueue(label: "concurrent", qos: .default, attributes: .concurrent)
    concurrentQueue.async {
        Thread.sleep(forTimeInterval: 2)
        print("concurrentAsync1: \(Thread.current)")
    }
    concurrentQueue.async {
        Thread.sleep(forTimeInterval: 2)
        print("concurrentAsync2: \(Thread.current)")
    }

    concurrentQueue.async(execute: barTask)
    //        barTask.wait()

    concurrentQueue.async {
        Thread.sleep(forTimeInterval: 2)
        print("concurrentAsync3: \(Thread.current)")
    }
    concurrentQueue.async {
        Thread.sleep(forTimeInterval: 2)
        print("concurrentAsync4: \(Thread.current)")
    }
}

打印:
concurrentAsync1: <NSThread: 0x60000180aa40>{number = 3, name = (null)}
concurrentAsync2: <NSThread: 0x60000182a540>{number = 4, name = (null)}
柵欄
barTask: <NSThread: 0x60000182a540>{number = 4, name = (null)}
concurrentAsync3: <NSThread: 0x60000182a540>{number = 4, name = (null)}
concurrentAsync4: <NSThread: 0x600001836d00>{number = 5, name = (null)}
semaphore

DispatchSemaphore豪筝,通常稱作信號量痰滋,顧名思義,它可以通過計數(shù)來標(biāo)識一個信號续崖,這個信號怎么用呢敲街,取決于任務(wù)的性質(zhì)。通常用于對同一個資源訪問的任務(wù)數(shù)進(jìn)行限制严望。例如多艇,控制同一時間寫文件的任務(wù)數(shù)量、控制端口訪問數(shù)量像吻、控制下載任務(wù)數(shù)量等峻黍。

func semaphore() {
    let queue = DispatchQueue.global()
    let sem = DispatchSemaphore(value: 2)

    let _ = sem.wait(timeout: DispatchTime.now() + 5)
    queue.async {
        Thread.sleep(forTimeInterval: 2)
        print("task 1 over")
        sem.signal()
    }

    sem.wait()
    queue.async {
        Thread.sleep(forTimeInterval: 2)
        print("task 2 over")
        sem.signal()
    }

    sem.wait()
    queue.async {
        Thread.sleep(forTimeInterval: 2)
        print("task 3 over")
        sem.signal()
    }
}

打印:
task 1 over
task 2 over
2秒后:
task 3 over
concurrentPerform

并行隊(duì)列利用多個線程執(zhí)行任務(wù)拨匆,可以提高程序執(zhí)行的效率姆涩。而迭代任務(wù)可以更高效地利用多核性能,它可以利用 CPU 當(dāng)前所有可用線程進(jìn)行計算(任務(wù)小也可能只用一個線程)惭每。如果一個任務(wù)可以分解為多個相似但獨(dú)立的子任務(wù)骨饿,那么迭代任務(wù)是提高性能最適合的選擇。
使用 concurrentPerform 方法執(zhí)行迭代任務(wù)台腥,迭代任務(wù)的后續(xù)任務(wù)需要等待它執(zhí)行完成才會繼續(xù)宏赘。

func concurrentPerform() {
    var reslut = [Int]()
    DispatchQueue.global().async {
        DispatchQueue.concurrentPerform(iterations: 100, execute: { (index) in
            if index % 13 == 0 {
                print("current: \(Thread.current)")
                DispatchQueue.main.async {
                    reslut.append(index)
                }
            }
        })

        DispatchQueue.main.async {
            print(reslut)
        }
    }
}

打印:
current: <NSThread: 0x6000024bc400>{number = 5, name = (null)}
current: <NSThread: 0x6000024a8d40>{number = 6, name = (null)}
current: <NSThread: 0x6000024a8d00>{number = 3, name = (null)}
current: <NSThread: 0x6000024bc300>{number = 4, name = (null)}
current: <NSThread: 0x6000024bc300>{number = 4, name = (null)}
current: <NSThread: 0x6000024a8d00>{number = 3, name = (null)}
current: <NSThread: 0x6000024bc400>{number = 5, name = (null)}
current: <NSThread: 0x6000024a8d40>{number = 6, name = (null)}
[39, 13, 0, 26, 65, 91, 78, 52]
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末黎侈,一起剝皮案震驚了整個濱河市察署,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌蜓竹,老刑警劉巖箕母,帶你破解...
    沈念sama閱讀 217,657評論 6 505
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異俱济,居然都是意外死亡嘶是,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,889評論 3 394
  • 文/潘曉璐 我一進(jìn)店門蛛碌,熙熙樓的掌柜王于貴愁眉苦臉地迎上來聂喇,“玉大人,你說我怎么就攤上這事∠L” “怎么了克饶?”我有些...
    開封第一講書人閱讀 164,057評論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長誊辉。 經(jīng)常有香客問我矾湃,道長,這世上最難降的妖魔是什么堕澄? 我笑而不...
    開封第一講書人閱讀 58,509評論 1 293
  • 正文 為了忘掉前任邀跃,我火速辦了婚禮,結(jié)果婚禮上蛙紫,老公的妹妹穿的比我還像新娘拍屑。我一直安慰自己,他們只是感情好坑傅,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,562評論 6 392
  • 文/花漫 我一把揭開白布僵驰。 她就那樣靜靜地躺著,像睡著了一般唁毒。 火紅的嫁衣襯著肌膚如雪蒜茴。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,443評論 1 302
  • 那天浆西,我揣著相機(jī)與錄音矮男,去河邊找鬼。 笑死室谚,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的崔泵。 我是一名探鬼主播秒赤,決...
    沈念sama閱讀 40,251評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼憎瘸!你這毒婦竟也來了入篮?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,129評論 0 276
  • 序言:老撾萬榮一對情侶失蹤幌甘,失蹤者是張志新(化名)和其女友劉穎潮售,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體锅风,經(jīng)...
    沈念sama閱讀 45,561評論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,779評論 3 335
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了涌韩。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片爬早。...
    茶點(diǎn)故事閱讀 39,902評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出训枢,到底是詐尸還是另有隱情托修,我是刑警寧澤,帶...
    沈念sama閱讀 35,621評論 5 345
  • 正文 年R本政府宣布恒界,位于F島的核電站睦刃,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏十酣。R本人自食惡果不足惜涩拙,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,220評論 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望婆誓。 院中可真熱鬧吃环,春花似錦、人聲如沸洋幻。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,838評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽文留。三九已至好唯,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間燥翅,已是汗流浹背骑篙。 一陣腳步聲響...
    開封第一講書人閱讀 32,971評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留森书,地道東北人靶端。 一個月前我還...
    沈念sama閱讀 48,025評論 2 370
  • 正文 我出身青樓,卻偏偏與公主長得像凛膏,于是被迫代替她去往敵國和親杨名。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,843評論 2 354

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