在軟件開(kāi)發(fā)中,我們經(jīng)常會(huì)遇到大量計(jì)算画畅,或者數(shù)據(jù)很多的下載,這些都需要異步線程處理宋距。還有一些有先后順序的操作轴踱,這些要到隊(duì)列的處理。另外乡革,App 的 UI 更新也必須要回到主線程中處理寇僧。Swift 實(shí)現(xiàn)多線程的方法主要有三種 NSThread
、NSOperationQueue
沸版、GCD
,不過(guò)我們通常用得比較多的是 GCD
方式
GCD(Grand Central Dispatch)
Grand Central Dispatch (GCD)
是 Apple 開(kāi)發(fā)的一個(gè)多核編程的解決方法嘁傀,基本概念就是dispatch queue
(調(diào)度隊(duì)列),queue 是一個(gè)對(duì)象视粮,它可以接受任務(wù)细办,并將任務(wù)以先到先執(zhí)行的順序來(lái)執(zhí)行。dispatch queue 可以是并發(fā)的或串行的。GCD 的底層依然是用線程實(shí)現(xiàn)笑撞,不過(guò)我們可以不用關(guān)注實(shí)現(xiàn)的細(xì)節(jié)岛啸。Swift3 之前 GCD 仍是面向過(guò)程的寫法,所以需要封裝一層再使用茴肥。Swift3 蘋果打成Dispatch
這個(gè) module.你可以通過(guò) import 進(jìn)行導(dǎo)入再使用坚踩。Swift4 之后,我們可以直接使用瓤狐。其優(yōu)點(diǎn)有如下幾點(diǎn):
- 易用:GCD 比 thread更簡(jiǎn)單易用瞬铸。基于 block 的特效使它能極為簡(jiǎn)單地在不同代碼作用域之間傳遞上下文础锐。
- 效率:GCD 實(shí)現(xiàn)功能輕量嗓节,優(yōu)雅,使得它在很多地方比專門創(chuàng)建消耗資源的線程更加實(shí)用且快捷皆警。
- 性能:GCD 自動(dòng)根據(jù)系統(tǒng)負(fù)載來(lái)增減線程數(shù)量拦宣,從而減少了上下文切換并增加了計(jì)算效率。
- 安全:無(wú)需加鎖或其他同步機(jī)制信姓。
GCD 可用于多核的并行運(yùn)算
GCD 會(huì)自動(dòng)利用更多的CPU內(nèi)核(比如雙核鸵隧、四核)
GCD 會(huì)自動(dòng)管理線程的生命周期(創(chuàng)建線程、調(diào)度任務(wù)财破、銷毀線程)
隊(duì)列和任務(wù)
* GCD 中有2個(gè)核心概念
任務(wù):執(zhí)行什么操作
隊(duì)列:用來(lái)存放任務(wù)
* GCD 的使用就2個(gè)步驟
定制任務(wù)
確定想做的事情
* 將任務(wù)添加到隊(duì)列中
GCD 會(huì)自動(dòng)將隊(duì)列中的任務(wù)取出掰派,放到對(duì)應(yīng)的線程中執(zhí)行
任務(wù)的取出遵循隊(duì)列的 FIFO 原則:先進(jìn)先出从诲,后進(jìn)后出
DispatchQueue
Dispatch
會(huì)自動(dòng)的根據(jù) CPU 的使用情況左痢,創(chuàng)建線程來(lái)執(zhí)行任務(wù),并且自動(dòng)的運(yùn)行到多核上系洛,提高程序的運(yùn)行效率俊性。對(duì)于開(kāi)發(fā)者來(lái)說(shuō),在 GCD 層面是沒(méi)有線程的概念的描扯,只有隊(duì)列(queue)定页。任務(wù)都是以block 的方式提交到對(duì)列上,然后 GCD 會(huì)自動(dòng)的創(chuàng)建線程池去執(zhí)行這些任務(wù)绽诚。
DispatchQueue
是一個(gè)類似線程的概念典徊,這里稱作對(duì)列隊(duì)列是一個(gè) FIFO
(先進(jìn)先出,后進(jìn)后出) 數(shù)據(jù)結(jié)構(gòu)恩够,意味著先提交到隊(duì)列的任務(wù)會(huì)先開(kāi)始執(zhí)行卒落。DispatchQueue
背后是一個(gè)由系統(tǒng)管理的線程池。
* 同步和異步的區(qū)別
同步(`sync`):只能在當(dāng)前線程中執(zhí)行任務(wù)蜂桶,不具備開(kāi)啟新線程的能力
異步 (`async`):可以在新的線程中執(zhí)行任務(wù)儡毕,具備開(kāi)啟新線程的能力
各種隊(duì)列的執(zhí)行效果
并發(fā)隊(duì)列 | 手動(dòng)創(chuàng)建的串行隊(duì)列 | 主隊(duì)列 | |
---|---|---|---|
同步(sync) | 沒(méi)有開(kāi)啟新線程、串行執(zhí)行任務(wù) | 沒(méi)有開(kāi)啟新線程扑媚、串行執(zhí)行任務(wù) | 沒(méi)有開(kāi)啟新線程腰湾、串行執(zhí)行任務(wù) |
異步(async) | 有開(kāi)啟新線程雷恃、并發(fā)執(zhí)行任務(wù) | 有開(kāi)啟新線程、串行執(zhí)行任務(wù) | 沒(méi)有開(kāi)啟新線费坊、串行執(zhí)行任務(wù) |
基本寫法倒槐,異步執(zhí)行回主線程寫法
DispatchQueue.global().async {
print("異步做某事: \(Thread.current)")
DispatchQueue.main.async {
print("回到主線程: \(Thread.current)")
}
}
DispatchQueue
一個(gè)對(duì)象,用于在應(yīng)用程序的主線程或后臺(tái)線程上串行或并發(fā)地管理任務(wù)的執(zhí)行附井。
let queue = DispatchQueue(label: "labelname", qos: .default, attributes: .concurrent, autoreleaseFrequency: .inherit)
-
label
隊(duì)列的標(biāo)識(shí)符导犹,方便調(diào)試 -
qos
隊(duì)列的 quality of service。用來(lái)指明隊(duì)列的 “重要性”羡忘,后文會(huì)詳細(xì)講到谎痢。 -
attributes
隊(duì)列的屬性。類型是DispatchQueue.Attributes,是一個(gè)結(jié)構(gòu)體卷雕,遵循了協(xié)議OptionSet节猿。意味著你可以這樣傳入第一個(gè)參數(shù)[.option1,.option2] -
autoreleaseFrequency
。顧名思義漫雕,自動(dòng)釋放頻率滨嘱。有些隊(duì)列是會(huì)在執(zhí)行完任務(wù)后自動(dòng)釋放的,有些比如 Timer 等是不會(huì)自動(dòng)釋放的浸间,是需要手動(dòng)釋放太雨。
隊(duì)列分類
* 系統(tǒng)創(chuàng)建的隊(duì)列
* 主隊(duì)列(對(duì)應(yīng)主線程)
* 全局隊(duì)列
* 用戶創(chuàng)建的隊(duì)列
* 串行與并行
// 主隊(duì)列
let mainQueue = DispatchQueue.main
// 全局隊(duì)列
let globalQueue = DispatchQueue.global()
// 用戶創(chuàng)建的隊(duì)列
let globalQueueWithQos = DispatchQueue.global(qos: .userInitiated)
串行與并行
串行(serial) :一次只能干一件事,挨個(gè)按順序執(zhí)行
并行(concurrent):多條流水線同時(shí)工作
/*
默認(rèn):列隊(duì)是串行的
.concurrent:列隊(duì)是并發(fā)的
.initiallyInactive:列隊(duì)不會(huì)自動(dòng)執(zhí)行魁蒜,需要開(kāi)發(fā)中手動(dòng)觸發(fā)
*/
// 串行隊(duì)列
let serialQueue = DispatchQueue(label: "serialQueue")
// 并行隊(duì)列
let concurrentQueue = DispatchQueue(label: "concurrentQueue",attributes:.concurrent)
異步與同步
- 異步(async)提交一段任務(wù)到隊(duì)列囊扳,并且立刻返回
func serial() {
// 異步
let serialQueue = DispatchQueue(label: "serialQueue")
print("\(Thread.current) Main queue Start")
serialQueue.async {
self.readDataTask(label: "1")
}
serialQueue.async {
self.readDataTask(label: "2")
}
print("\(Thread.current) Main queue End")
}
// 讀數(shù)據(jù)
func readDataTask(label: String){
print("\(Thread.current) Start sync task\(label)")
sleep(2)
print("\(Thread.current) End sync task\(label)")
}
/* 輸出結(jié)果
<NSThread: 0x6000021badc0>{number = 1, name = main} Main queue Start
<NSThread: 0x6000021e0cc0>{number = 4, name = (null)} Start sync task1
<NSThread: 0x6000021badc0>{number = 1, name = main} Main queue End
<NSThread: 0x6000021e0cc0>{number = 4, name = (null)} End sync task1
<NSThread: 0x6000021e0cc0>{number = 4, name = (null)} Start sync task2
<NSThread: 0x6000021e0cc0>{number = 4, name = (null)} End sync task2
*/
系統(tǒng)在導(dǎo)步串行隊(duì)列中開(kāi)了一個(gè)
線程4
處理,可以看到每一條線程兜看,是完成 task1 之后才會(huì)開(kāi)始 task2锥咸,而且主線程線程1
沒(méi)有阻塞的運(yùn)行完。
總結(jié)一下细移,主線程按照順序提交任務(wù)1搏予,任務(wù)2到 serialQueue,瞬間執(zhí)行完畢弧轧,并沒(méi)有被阻塞雪侥。
在 serialQueue 上先執(zhí)行任務(wù)1,任務(wù)1執(zhí)行完畢后再執(zhí)行任務(wù)2.
- 同步 (sync) 提交一段任務(wù)到隊(duì)列精绎,并且阻塞當(dāng)前線程速缨,任務(wù)結(jié)束后當(dāng)前線程繼續(xù)執(zhí)行
func concurrent() {
print("\(Thread.current) Main queue Start")
// 同步
let concurrentQueue = DispatchQueue(label: "concurrent", attributes: .concurrent)
concurrentQueue.async {
self.readDataTask(label: "3")
}
concurrentQueue.async {
self.readDataTask(label: "4")
}
print("\(Thread.current) Main queue End")
}
// 讀數(shù)據(jù)
func readDataTask(label: String){
print("\(Thread.current) Start sync task\(label)")
sleep(2)
print("\(Thread.current) End sync task\(label)")
}
/* 輸出結(jié)果
<NSThread: 0x60000100cfc0>{number = 1, name = main} Main queue Start
<NSThread: 0x60000100cfc0>{number = 1, name = main} Main queue End
<NSThread: 0x600001040680>{number = 4, name = (null)} Start sync task4
<NSThread: 0x60000107ad80>{number = 5, name = (null)} Start sync task3
<NSThread: 0x60000107ad80>{number = 5, name = (null)} End sync task3
<NSThread: 0x600001040680>{number = 4, name = (null)} End sync task4
*/
主線程依然沒(méi)有被阻塞。
在 concurrentQueue 隊(duì)列上捺典,兩個(gè)任務(wù)按照提交的次序開(kāi)始鸟廓,兩個(gè)任務(wù)并發(fā)的執(zhí)行了。
sync 是一個(gè)強(qiáng)大但是容易被忽視的函數(shù)。使用 sync引谜,可以方便的進(jìn)行線程間同步牍陌。但是,有一點(diǎn)要注意员咽,sync 容易造成死鎖.
DispatchQoS (quality of service) 服務(wù)質(zhì)量
適用于任務(wù)的服務(wù)質(zhì)量或執(zhí)行優(yōu)先級(jí)毒涧。
優(yōu)先級(jí)由最低的 background
到最高的 userInteractive
共五個(gè),還有一個(gè)為定義的 unspecified
.
-
background
:最低優(yōu)先級(jí)贝室,等同于DISPATCH_QUEUE_PRIORITY_BACKGROUND
. 用戶不可見(jiàn)契讲,比如:在后臺(tái)存儲(chǔ)大量數(shù)據(jù) -
utility
:優(yōu)先級(jí)等同于DISPATCH_QUEUE_PRIORITY_LOW
,可以執(zhí)行很長(zhǎng)時(shí)間滑频,再通知用戶結(jié)果捡偏。比如:下載一個(gè)大文件,網(wǎng)絡(luò)峡迷,計(jì)算 -
default
:默認(rèn)優(yōu)先級(jí),優(yōu)先級(jí)等同于DISPATCH_QUEUE_PRIORITY_DEFAULT
银伟,建議大多數(shù)情況下使用默認(rèn)優(yōu)先級(jí) -
userInitiated
:優(yōu)先級(jí)等同于DISPATCH_QUEUE_PRIORITY_HIGH
,需要立刻的結(jié)果 -
userInteractive
:用戶交互相關(guān),為了好的用戶體驗(yàn)绘搞,任務(wù)需要立馬執(zhí)行彤避。使用該優(yōu)先級(jí)用于 UI 更新,事件處理和小工作量任務(wù)夯辖,在主線程執(zhí)行
Qos指定了列隊(duì)工作的優(yōu)先級(jí)琉预,系統(tǒng)會(huì)根據(jù)優(yōu)先級(jí)來(lái)調(diào)度工作,越高的優(yōu)先級(jí)能夠越快被執(zhí)行蒿褂,但是也會(huì)消耗功能圆米,所以準(zhǔn)確的指定優(yōu)先級(jí)能夠保證app有效的使用資源。
QoS 可以在創(chuàng)建 queue 時(shí)添加或者在提交 block 的時(shí)候贮缅,指定 QoS
DispatchQueue.global().async(qos: .background) {
// code
}
DispatchWorkItem
想要執(zhí)行的工作以某種方式進(jìn)行封裝榨咐,使您可以附加完成句柄或執(zhí)行依賴項(xiàng)。通俗的說(shuō)就是 DispatchWorkItem 把任務(wù)封裝成一個(gè)對(duì)象谴供。
let item = DispatchWorkItem {
// 任務(wù)
}
DispatchQueue.global().async(execute: item)
也可以初始化時(shí)指定更多的參數(shù)
DispatchWorkItem(qos: .userInitiated, flags: [.assignCurrentContext,.barrier]) {
// 任務(wù)
}
- 第一個(gè)參數(shù)表示 QoS。
- 第二個(gè)參數(shù)類型為 DispatchWorkItemFlags齿坷。指定這個(gè)任務(wù)的配飾信息
- 第三個(gè)參數(shù)則是實(shí)際的任務(wù) block
DispatchWorkItemFlags 的參數(shù)分為兩組
執(zhí)行情況
- barrier 屏障桂肌,后面詳解
- detached
- assignCurrentContext
QoS覆蓋信息
- noQoS // 沒(méi)有QoS
- inheritQoS // 繼承Queue的QoS
- enforceQoS // 自己的QoS覆蓋Queue
DispatchGroup
DispatchGroup
用于管理一組任務(wù)的執(zhí)行,然后監(jiān)聽(tīng)任務(wù)的完成永淌,進(jìn)而執(zhí)行后續(xù)操作崎场。比如:同一個(gè)頁(yè)面發(fā)送多個(gè)網(wǎng)絡(luò)請(qǐng)求,等待所有結(jié)果請(qǐng)求成功刷新 UI 界面
notify(依賴任務(wù))
func groupNotify() {
let queue = DispatchQueue.global()
let group = DispatchGroup()
queue.async(group: group, qos: .default, flags: [], execute: {
for _ in 0...4 {
print("\(Thread.current) 耗時(shí)任務(wù)一")
}
})
queue.async(group: group, qos: .default, flags: [], execute: {
for _ in 0...4 {
print("\(Thread.current) 耗時(shí)任務(wù)二")
}
})
// 執(zhí)行完上面的兩個(gè)耗時(shí)操作, 回到 queue 隊(duì)列中執(zhí)行下一步的任務(wù)
group.notify(queue: queue) {
print("\(Thread.current) 回到該隊(duì)列中執(zhí)行")
}
}
/* 輸出結(jié)果
<NSThread: 0x600002001800>{number = 3, name = (null)} 耗時(shí)任務(wù)一
<NSThread: 0x60000203ec40>{number = 5, name = (null)} 耗時(shí)任務(wù)二
<NSThread: 0x600002001800>{number = 3, name = (null)} 耗時(shí)任務(wù)一
<NSThread: 0x600002001800>{number = 3, name = (null)} 耗時(shí)任務(wù)一
<NSThread: 0x60000203ec40>{number = 5, name = (null)} 耗時(shí)任務(wù)二
<NSThread: 0x600002001800>{number = 3, name = (null)} 耗時(shí)任務(wù)一
<NSThread: 0x600002001800>{number = 3, name = (null)} 耗時(shí)任務(wù)一
<NSThread: 0x60000203ec40>{number = 5, name = (null)} 耗時(shí)任務(wù)二
<NSThread: 0x60000203ec40>{number = 5, name = (null)} 耗時(shí)任務(wù)二
<NSThread: 0x60000203ec40>{number = 5, name = (null)} 耗時(shí)任務(wù)二
<NSThread: 0x60000203ec40>{number = 5, name = (null)} 回到該隊(duì)列中執(zhí)行
*/
wait(任務(wù)等待)
func groupWait() {
let queue = DispatchQueue.global()
let group = DispatchGroup()
queue.async(group: group, qos: .default, flags: [], execute: {
for _ in 0...4 {
print("\(Thread.current) 耗時(shí)任務(wù)一")
}
})
queue.async(group: group, qos: .default, flags: [], execute: {
for _ in 0...4 {
print("\(Thread.current) 耗時(shí)任務(wù)二")
sleep(1)
}
})
// 等待上面任務(wù)執(zhí)行遂蛀,會(huì)阻塞當(dāng)前線程谭跨,超時(shí)就執(zhí)行下面的,上面的繼續(xù)執(zhí)行◇χ妫可以無(wú)限等待 .distantFuture
let result = group.wait(timeout: .now()+10)
switch result {
case .success:
print("\(Thread.current) 不超時(shí), 上面的兩個(gè)任務(wù)都執(zhí)行完")
case .timedOut:
print("\(Thread.current) 超時(shí)了, 上面的任務(wù)還沒(méi)執(zhí)行完執(zhí)行這了")
}
print("\(Thread.current) 完成...")
}
/* 輸出結(jié)果
<NSThread: 0x600002084a80>{number = 3, name = (null)} 耗時(shí)任務(wù)一
<NSThread: 0x600002083700>{number = 5, name = (null)} 耗時(shí)任務(wù)二
<NSThread: 0x600002084a80>{number = 3, name = (null)} 耗時(shí)任務(wù)一
<NSThread: 0x600002084a80>{number = 3, name = (null)} 耗時(shí)任務(wù)一
<NSThread: 0x600002084a80>{number = 3, name = (null)} 耗時(shí)任務(wù)一
<NSThread: 0x600002084a80>{number = 3, name = (null)} 耗時(shí)任務(wù)一
<NSThread: 0x600002083700>{number = 5, name = (null)} 耗時(shí)任務(wù)二
<NSThread: 0x600002083700>{number = 5, name = (null)} 耗時(shí)任務(wù)二
<NSThread: 0x600002083700>{number = 5, name = (null)} 耗時(shí)任務(wù)二
<NSThread: 0x600002083700>{number = 5, name = (null)} 耗時(shí)任務(wù)二
<NSThread: 0x6000020da1c0>{number = 1, name = main} 不超時(shí), 上面的兩個(gè)任務(wù)都執(zhí)行完
<NSThread: 0x6000020da1c0>{number = 1, name = main} 完成...
*/
手動(dòng)管理
group
的計(jì)數(shù)器蛮瞄,enter
和leave
必須配對(duì)
由于是并發(fā)執(zhí)行異步任務(wù),所以任務(wù)的先后次序是不一定的谆扎,看起來(lái)符合我們的需求挂捅,最后接受通知然后可以刷新 UI 操作。但是真實(shí)的網(wǎng)絡(luò)請(qǐng)求是異步堂湖、耗時(shí)的闲先,并不是立馬就返回,所以我們使用 asyncAfter
模擬延時(shí)看看无蜂,將任務(wù)1延時(shí)一秒執(zhí)行:
// 將任務(wù)1延時(shí)一秒執(zhí)行
func groupNotify() {
let queue = DispatchQueue.global()
let group = DispatchGroup()
queue.async(group: group, qos: .default, flags: [], execute: {
// 增加耗時(shí)
DispatchQueue.main.asyncAfter(deadline: .now() + 1, execute: {
for _ in 0...4 {
print("\(Thread.current) 耗時(shí)任務(wù)一")
}
})
})
queue.async(group: group, qos: .default, flags: [], execute: {
for _ in 0...4 {
print("\(Thread.current) 耗時(shí)任務(wù)二")
}
})
// 執(zhí)行完上面的兩個(gè)耗時(shí)操作, 回到 queue 隊(duì)列中執(zhí)行下一步的任務(wù)
group.notify(queue: queue) {
print("\(Thread.current) 回到該隊(duì)列中執(zhí)行")
}
}
/* 輸出結(jié)果
<NSThread: 0x6000020aa9c0>{number = 6, name = (null)} 耗時(shí)任務(wù)二
<NSThread: 0x6000020aa9c0>{number = 6, name = (null)} 耗時(shí)任務(wù)二
<NSThread: 0x6000020aa9c0>{number = 6, name = (null)} 耗時(shí)任務(wù)二
<NSThread: 0x6000020aa9c0>{number = 6, name = (null)} 耗時(shí)任務(wù)二
<NSThread: 0x6000020aa9c0>{number = 6, name = (null)} 耗時(shí)任務(wù)二
<NSThread: 0x6000020a7f00>{number = 7, name = (null)} 回到該隊(duì)列中執(zhí)行
<NSThread: 0x6000020c8700>{number = 1, name = main} 耗時(shí)任務(wù)一
<NSThread: 0x6000020c8700>{number = 1, name = main} 耗時(shí)任務(wù)一
<NSThread: 0x6000020c8700>{number = 1, name = main} 耗時(shí)任務(wù)一
<NSThread: 0x6000020c8700>{number = 1, name = main} 耗時(shí)任務(wù)一
<NSThread: 0x6000020c8700>{number = 1, name = main} 耗時(shí)任務(wù)一
*/
所以伺糠,為了真正實(shí)現(xiàn)預(yù)期的效果,我們需要配合 group
的 enter
和 leave
兩個(gè)函數(shù)斥季。每次執(zhí)行 group.enter()
表示一個(gè)任務(wù)被加入到列隊(duì)組 group
中退盯,此時(shí) group
中的任務(wù)的引用計(jì)數(shù)會(huì)加1,當(dāng)使用 group.leave()
泻肯,表示 group
中的一個(gè)任務(wù)完成渊迁,group
中任務(wù)的引用計(jì)數(shù)減1.當(dāng) group
列隊(duì)組里面的任務(wù)引用計(jì)數(shù)為0時(shí),會(huì)通知 notify
函數(shù)灶挟,任務(wù)執(zhí)行完成琉朽。
注意:
enter()
和leave()
成對(duì)出現(xiàn)的。
func enterLeaveGroup() {
let group = DispatchGroup()
let queue = DispatchQueue.global()
// 把該任務(wù)添加到組隊(duì)列中執(zhí)行
group.enter()
queue.async(group: group, qos: .default, flags: []) {
// 增加耗時(shí)
DispatchQueue.main.asyncAfter(deadline: .now() + 1, execute: {
for _ in 0...4 {
print("\(Thread.current) 耗時(shí)任務(wù)一")
}
// 執(zhí)行完之后從組隊(duì)列中移除
group.leave()
})
}
// 把該任務(wù)添加到組隊(duì)列中執(zhí)行
group.enter()
queue.async(group: group, qos: .default, flags: []) {
for _ in 0...4 {
print("\(Thread.current) 耗時(shí)任務(wù)二")
}
// 執(zhí)行完之后從組隊(duì)列中移除
group.leave()
}
// 當(dāng)上面所有的任務(wù)執(zhí)行完之后通知
group.notify(queue: queue) {
print("\(Thread.current) 所有的任務(wù)執(zhí)行完了")
}
}
/* 輸出結(jié)果
<NSThread: 0x6000013dd980>{number = 6, name = (null)} 耗時(shí)任務(wù)二
<NSThread: 0x6000013dd980>{number = 6, name = (null)} 耗時(shí)任務(wù)二
<NSThread: 0x6000013dd980>{number = 6, name = (null)} 耗時(shí)任務(wù)二
<NSThread: 0x6000013dd980>{number = 6, name = (null)} 耗時(shí)任務(wù)二
<NSThread: 0x6000013dd980>{number = 6, name = (null)} 耗時(shí)任務(wù)二
<NSThread: 0x6000013862c0>{number = 1, name = main} 耗時(shí)任務(wù)一
<NSThread: 0x6000013862c0>{number = 1, name = main} 耗時(shí)任務(wù)一
<NSThread: 0x6000013862c0>{number = 1, name = main} 耗時(shí)任務(wù)一
<NSThread: 0x6000013862c0>{number = 1, name = main} 耗時(shí)任務(wù)一
<NSThread: 0x6000013862c0>{number = 1, name = main} 耗時(shí)任務(wù)一
<NSThread: 0x6000013ea2c0>{number = 7, name = (null)} 所有的任務(wù)執(zhí)行完了
*/
asyncAfter 延時(shí)處理
使用 asyncAfter
來(lái)提交任務(wù)進(jìn)行延遲稚铣。之前是使用 dispatch_time
,現(xiàn)在是使用 DispatchTime
對(duì)象表示箱叁。可以使用靜態(tài)方法 now
獲得當(dāng)前時(shí)間惕医,然后再通過(guò)加上 DispatchTimeInterval
枚舉獲得一個(gè)需要延遲的時(shí)間耕漱。注意:僅僅是用于在具體時(shí)間執(zhí)行任務(wù),不要在資源競(jìng)爭(zhēng)的情況下使用抬伺。并且在主列隊(duì)使用螟够。
let delay = DispatchTime.now() + DispatchTimeInterval.seconds(10)
DispatchQueue.main.asyncAfter(deadline: delay) {
// 延遲執(zhí)行
}
因?yàn)樵?DispatchTime
中自定義了“+”號(hào), 我們進(jìn)一步簡(jiǎn)化
public func +(time: DispatchTime, seconds: Double) -> DispatchTime
let delay = DispatchTime.now() + 10
DispatchQueue.main.asyncAfter(deadline: delay) {
// 延遲執(zhí)行
}
DispatchSemaphore 信號(hào)量
Semaphore
是保證線程安全的一種方式,而且繼 OSSpinLock
不再安全后峡钓,Semaphore
似乎成為了最快的加鎖的方式妓笙。
信號(hào)量在多線程開(kāi)發(fā)中被廣泛使用,當(dāng)一個(gè)線程在進(jìn)入一段關(guān)鍵代碼之前能岩,線程必須獲取一個(gè)信號(hào)量寞宫,一旦該關(guān)鍵代碼段完成了,那么該線程必須釋放信號(hào)量拉鹃。其它想進(jìn)入該關(guān)鍵代碼段的線程必須等待前面的線程釋放信號(hào)量辈赋。
信號(hào)量的具體做法是:當(dāng)信號(hào)計(jì)數(shù)大于0時(shí)鲫忍,每條進(jìn)來(lái)的線程使計(jì)數(shù)減1,直到變?yōu)?钥屈,變?yōu)?后其他的線程將進(jìn)不來(lái)悟民,處于等待狀態(tài);執(zhí)行完任務(wù)的線程釋放信號(hào)焕蹄,使計(jì)數(shù)加1逾雄,如此循環(huán)下去。
func semaphore() {
// 創(chuàng)建信號(hào)量腻脏,參數(shù):信號(hào)量的初值鸦泳,如果小于0則會(huì)返回 NULL, value 表示最多幾個(gè)資源可訪問(wèn)
let semaphore = DispatchSemaphore(value: 2)
let queue = DispatchQueue.global()
// 任務(wù)1
queue.async {
// 等待降低信號(hào)量
semaphore.wait()
print("運(yùn)行任務(wù)1")
sleep(1)
print("結(jié)果任務(wù)1")
// 提高信號(hào)量
semaphore.signal()
}
// 任務(wù)2
queue.async {
// 等待降低信號(hào)量
semaphore.wait()
print("運(yùn)行任務(wù)2")
sleep(1)
print("結(jié)果任務(wù)2")
// 提高信號(hào)量
semaphore.signal()
}
// 任務(wù)3
queue.async {
// 等待降低信號(hào)量
semaphore.wait()
print("運(yùn)行任務(wù)3")
sleep(1)
print("結(jié)果任務(wù)3")
// 提高信號(hào)量
semaphore.signal()
}
}
/* 輸出結(jié)果:
運(yùn)行任務(wù)2
運(yùn)行任務(wù)1
結(jié)果任務(wù)1
結(jié)果任務(wù)2
運(yùn)行任務(wù)3
結(jié)果任務(wù)3
*/
由于設(shè)定的信號(hào)值為2,先執(zhí)行兩個(gè)線程永品,等執(zhí)行完一個(gè)做鹰,才會(huì)繼續(xù)執(zhí)行下一個(gè),保證同一時(shí)間執(zhí)行的線程數(shù)不超過(guò)2鼎姐。
Barrier 屏障
GCD
里的 Barrier
和 NSOperationQueue
的 dependency
比較接近钾麸,C 任務(wù)開(kāi)始之前需要 A 任務(wù)完成,或者 A 和 B 任務(wù)完成炕桨。
func barrier() {
let queue = DispatchQueue(label: "barrier", attributes: .concurrent)
queue.async {
print("任務(wù)A")
}
queue.async {
sleep(3)
print("任務(wù)B")
}
// 這里 barrier饭尝,必須等任務(wù)C完成后,才走后面任務(wù)D
queue.async(flags: .barrier) {
sleep(2)
print("任務(wù)C")
}
queue.async {
print("任務(wù)D")
}
}
假如我們有一個(gè)并發(fā)的列隊(duì)用來(lái)讀寫一個(gè)數(shù)據(jù)對(duì)象献宫,如果這個(gè)列隊(duì)的操作是讀钥平,那么可以同時(shí)多個(gè)進(jìn)行。如果有寫的操作姊途,則必須保證在執(zhí)行寫操作時(shí)涉瘾,不會(huì)有讀取的操作執(zhí)行,必須等待寫操作完成之后再開(kāi)始讀取操作捷兰,否則會(huì)造成讀取的數(shù)據(jù)出錯(cuò)立叛,經(jīng)典的讀寫問(wèn)題。這里我們就可以使用 barrier, 通過(guò)在并發(fā)代碼中使用 barrier 將能夠保證寫操作在所有讀取操作完成之后進(jìn)行贡茅,而且確保寫操作執(zhí)行完成之后再開(kāi)始后續(xù)的讀取操作秘蛇。
// 保證寫入時(shí),不能讀數(shù)據(jù)
let item = DispatchWorkItem(qos: .default, flags: .barrier) {
// write data
}
let dataQueue = DispatchQueue(label: "queue", attributes: .concurrent)
dataQueue.async(execute: item)
Suspend / Resume
Suspend
可以掛起一個(gè)線程友扰,即暫停線程彤叉,但是仍然暫用資源,只是不執(zhí)行
Resume
回復(fù)線程村怪,即繼續(xù)執(zhí)行掛起的線程。
循環(huán)執(zhí)行任務(wù)
調(diào)用 concurrentPerform()
// 并發(fā)執(zhí)行5次
DispatchQueue.concurrentPerform(iterations: 5) {
print("\($0)")
}
DispatchSource
DispatchSource提高了相關(guān)的API來(lái)監(jiān)控低級(jí)別的系統(tǒng)對(duì)象浮庐,比如:Mach ports, Unix descriptors, Unix signals, VFS nodes甚负。并且能夠異步提交事件到派發(fā)列隊(duì)執(zhí)行柬焕。
簡(jiǎn)單定時(shí)器
func timer() {
// 定時(shí)時(shí)間
var timeCount = 60
// 創(chuàng)建時(shí)間源
let timer = DispatchSource.makeTimerSource(queue: DispatchQueue.global())
// 每秒調(diào)用一次
timer.schedule(deadline: .now(), repeating: .seconds(1))
timer.setEventHandler {
timeCount -= 1
if timeCount <= 0 { timer.cancel() }
DispatchQueue.main.async {
print("update UI or other task")
}
}
// 啟動(dòng)時(shí)間源
timer.resume()
}
注意事項(xiàng)
線程死鎖
不要在主列隊(duì)中執(zhí)行同步任務(wù),這樣會(huì)造成死鎖問(wèn)題梭域。
參考文章:
Apple Documentation
Swift4 - GCD的使用
iOS多線程 Swift4 GCD深入解析
GCD精講(Swift 3&4)
iOS GCD中級(jí)篇 - dispatch_semaphore(信號(hào)量)的理解及使用