基本概念
什么是 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]