1. 前言
之前寫了一遍文章介紹了Objective-C中GCD的用法: [iOS]多線程--GCD, 現(xiàn)在轉(zhuǎn)戰(zhàn)到Swift的戰(zhàn)場, 根據(jù)前者一些知識點(diǎn), 講下使用Swift如何實(shí)現(xiàn).
2. 隊(duì)列
2.1 串行隊(duì)列
創(chuàng)建串行隊(duì)列的方式很簡單:
DispatchQueue(label: <#T##String#>)
- label : 隊(duì)列的標(biāo)識符
例如:
let queue = DispatchQueue(label: "QueueIdentifier")
如果想指定串行隊(duì)列的優(yōu)先級, 可使用下面的方法來創(chuàng)建:
let queue = DispatchQueue(label: "QueueIdentifier", qos: .userInitiated)
參數(shù)qos: 用于指定隊(duì)列的優(yōu)先級, 是個枚舉:
- .userInteractive
- .userInitiated
- .default
- .utility
- .background
- .unspecified
優(yōu)先級, 由上往下依次降低
一個最常用的串行隊(duì)列--主隊(duì)列:
DispatchQueue.main
2.2 并行隊(duì)列
并行隊(duì)列, 可以使用下面這個方法進(jìn)行創(chuàng)建:
DispatchQueue(label: <#T##String#>, qos: <#T##DispatchQoS#>, attributes: <#T##DispatchQueue.Attributes#>)
和創(chuàng)建串行隊(duì)列的方法一樣, 只不過多了一個參數(shù)attributes, 傳 .concurrent 即可創(chuàng)建一個并行隊(duì)列
let queue1 = DispatchQueue(label: "并行隊(duì)列的創(chuàng)建", qos: .default, attributes: .concurrent)
在使用的時候, 我們一般不去創(chuàng)建并行隊(duì)列, 而是使用系統(tǒng)為我們提供的全局的并行隊(duì)列:
// 獲取全局并行隊(duì)列
let queue = DispatchQueue.global()
在獲取的時候我們也可以指定其優(yōu)先級:
let que = DispatchQueue.global(qos: .default)
注意:
在創(chuàng)建串行并行隊(duì)列的時候, 參數(shù)attributes, 可以指定創(chuàng)建的是串行還是并行隊(duì)列, 他還有一個值: .initiallyInactive, 即: 創(chuàng)建的時候, 是處于不活躍狀態(tài), 即不會執(zhí)行任務(wù), 需要手動調(diào)用activate()來激活隊(duì)列執(zhí)行任務(wù);
例如:
print("當(dāng)前線程: \(Thread.current)")
let queue = DispatchQueue(label: "開始不活躍的串行隊(duì)列", attributes: .initiallyInactive)
queue.async {
print("執(zhí)行了么?")
}
print("任務(wù)結(jié)束")
這個示例, 程序會crash, 應(yīng)該這樣:
print("當(dāng)前線程: \(Thread.current)")
let queue = DispatchQueue(label: "開始不活躍的串行隊(duì)列", attributes: .initiallyInactive)
queue.async {
print("執(zhí)行了么?")
}
queue.activate()
print("任務(wù)結(jié)束")
上面這個是, 串行的不活躍隊(duì)列, 如果想創(chuàng)建并行的不活躍隊(duì)列呢? 可以這樣:
print("當(dāng)前線程: \(Thread.current)")
let queue = DispatchQueue(label: "開始不活躍的并行隊(duì)列", attributes: [.concurrent, .initiallyInactive])
queue.async {
print("執(zhí)行了么?")
}
queue.activate()
print("任務(wù)結(jié)束")
2.3 同步, 異步
同步/異步的執(zhí)行, 只需要使用隊(duì)列的實(shí)例對象調(diào)用sync(同步)/async(異步)方法即可, 這里可以使用閉包, 也可以使用DispatchWorkItem對象
queue.sync {
<#code#>
}
queue.async(execute: <#T##DispatchWorkItem#>)
3. 同步, 異步, 串行, 并發(fā)組合測試
測試一: 用同步函數(shù)往串行隊(duì)列中添加任務(wù)
不會開啟新的線程:
print("當(dāng)前線程: \(Thread.current)")
let queue = DispatchQueue(label: "創(chuàng)建串行隊(duì)列")
queue.sync {
print("串行隊(duì)列中同步執(zhí)行的第1個任務(wù): \(Thread.current)")
sleep(4)
}
queue.sync {
print("串行隊(duì)列中同步執(zhí)行的第2個任務(wù): \(Thread.current)")
sleep(2)
}
queue.sync {
print("串行隊(duì)列中同步執(zhí)行的第3個任務(wù): \(Thread.current)")
}
控制臺輸出:
當(dāng)前線程: <NSThread: 0x600000065900>{number = 1, name = main}
串行隊(duì)列中同步執(zhí)行的第1個任務(wù): <NSThread: 0x600000065900>{number = 1, name = main}
串行隊(duì)列中同步執(zhí)行的第2個任務(wù): <NSThread: 0x600000065900>{number = 1, name = main}
串行隊(duì)列中同步執(zhí)行的第3個任務(wù): <NSThread: 0x600000065900>{number = 1, name = main}
可以看出, 沒有開啟新的線程, 同時也是按照順序依次執(zhí)行的;
測試二: 用異步函數(shù)往串行隊(duì)列中添加任務(wù)
會開啟線程乡洼,但是只開啟一個線程:
print("當(dāng)前線程: \(Thread.current)")
let queue = DispatchQueue(label: "創(chuàng)建串行隊(duì)列")
queue.async {
print("串行隊(duì)列中同步執(zhí)行的第1個任務(wù): \(Thread.current)")
sleep(4)
}
queue.async {
print("串行隊(duì)列中同步執(zhí)行的第2個任務(wù): \(Thread.current)")
sleep(2)
}
queue.async {
print("串行隊(duì)列中同步執(zhí)行的第3個任務(wù): \(Thread.current)")
}
控制臺輸出:
當(dāng)前線程: <NSThread: 0x608000064000>{number = 1, name = main}
串行隊(duì)列中同步執(zhí)行的第1個任務(wù): <NSThread: 0x608000071dc0>{number = 4, name = (null)}
串行隊(duì)列中同步執(zhí)行的第2個任務(wù): <NSThread: 0x608000071dc0>{number = 4, name = (null)}
串行隊(duì)列中同步執(zhí)行的第3個任務(wù): <NSThread: 0x608000071dc0>{number = 4, name = (null)}
雖然是異步函數(shù), 但是添加到了串行隊(duì)列里, 只開啟了一個新的線程, 添加到其中的任務(wù)還是按順序依次執(zhí)行的
測試三: 用同步函數(shù)往并發(fā)隊(duì)列中添加任務(wù)
不會開啟新的線程((同步函數(shù)不具備開啟新線程的能力))狂票,并發(fā)隊(duì)列失去了并發(fā)的功能:
print("當(dāng)前線程: \(Thread.current)")
let queue = DispatchQueue(label: "創(chuàng)建并行隊(duì)列", attributes: .concurrent)
queue.sync {
print("串行隊(duì)列中同步執(zhí)行的第1個任務(wù): \(Thread.current)")
sleep(4)
}
queue.sync {
print("串行隊(duì)列中同步執(zhí)行的第2個任務(wù): \(Thread.current)")
sleep(2)
}
queue.sync {
print("串行隊(duì)列中同步執(zhí)行的第3個任務(wù): \(Thread.current)")
}
控制臺輸出:
當(dāng)前線程: <NSThread: 0x600000077b80>{number = 1, name = main}
串行隊(duì)列中同步執(zhí)行的第1個任務(wù): <NSThread: 0x600000077b80>{number = 1, name = main}
串行隊(duì)列中同步執(zhí)行的第2個任務(wù): <NSThread: 0x600000077b80>{number = 1, name = main}
串行隊(duì)列中同步執(zhí)行的第3個任務(wù): <NSThread: 0x600000077b80>{number = 1, name = main}
可以看出, 雖然使用的是并發(fā)隊(duì)列, 但是使用的是同步函數(shù), 由于同步函數(shù)沒有開啟新線程的能力, 所以并發(fā)隊(duì)列就失去了并發(fā)性, 按照任務(wù)的添加順序, 順序執(zhí)行;
測試四. 用異步函數(shù)往并發(fā)隊(duì)列中添加任務(wù)
同時開啟多個子線程執(zhí)行任務(wù):
print("當(dāng)前線程: \(Thread.current)")
let queue = DispatchQueue(label: "創(chuàng)建并行隊(duì)列", attributes: .concurrent)
queue.async {
print("串行隊(duì)列中同步執(zhí)行的第1個任務(wù): \(Thread.current)")
sleep(4)
}
queue.async {
print("串行隊(duì)列中同步執(zhí)行的第2個任務(wù): \(Thread.current)")
sleep(2)
}
queue.async {
print("串行隊(duì)列中同步執(zhí)行的第3個任務(wù): \(Thread.current)")
}
控制臺輸出:
當(dāng)前線程: <NSThread: 0x60000007c6c0>{number = 1, name = main}
串行隊(duì)列中同步執(zhí)行的第2個任務(wù): <NSThread: 0x600000270380>{number = 4, name = (null)}
串行隊(duì)列中同步執(zhí)行的第3個任務(wù): <NSThread: 0x60800027dec0>{number = 5, name = (null)}
串行隊(duì)列中同步執(zhí)行的第1個任務(wù): <NSThread: 0x60000026b600>{number = 3, name = (null)}
可以看出, 這里開啟了三個子線程來執(zhí)行任務(wù), 互相之間沒有影響.
只有在并發(fā)隊(duì)列異步執(zhí)行的時候才能真正起到 并發(fā)的作用.
測試五. 控制最大并發(fā)數(shù)
在進(jìn)行并發(fā)操作的時候, 如果任務(wù)過多, 開啟很多線程, 會導(dǎo)致APP卡死. 所以, 我們要控制最大并發(fā)數(shù), 這就用到了信號量DispatchSemaphore, 我們可以這樣創(chuàng)建一個信號量:
let semaphore = DispatchSemaphore.init(value: 10)
參數(shù)為最大并發(fā)執(zhí)行的任務(wù)數(shù), 也即是信號量.
信號量減一:
semaphore.wait()
// 如果信號量大于1, 則會繼續(xù)執(zhí)行, 如果信號量等于0, 會等待timeout的時間, 在等待期間被semaphore.signal()加一了, 這里會繼續(xù)執(zhí)行, 并將信號量減一
semaphore.wait(timeout: <#T##DispatchTime#>)
上面函數(shù)的返回值為DispatchTimeoutResult, 是個枚舉:
- success
- timedOut
DispatchTime的值可使用: .now() , 或者 .distantFuture
也可以創(chuàng)建:
DispatchTime.init(uptimeNanoseconds: <#T##UInt64#>)
這里的參數(shù)單位為納秒: 1s = 1000*1000*100ns
信號量加一:
semaphore.signal()
一個應(yīng)用:
print("當(dāng)前線程: \(Thread.current)")
let group = DispatchGroup.init()
let queue = DispatchQueue.global()
let semaphore = DispatchSemaphore.init(value: 10)
for i in 0...100 {
let result = semaphore.wait(timeout: .distantFuture)
if result == .success {
queue.async(group: group, execute: {
print("隊(duì)列執(zhí)行\(zhòng)(i)--\(Thread.current)")
// 模擬執(zhí)行任務(wù)時間
sleep(2)
// 任務(wù)結(jié)束, 信號量+1
semaphore.signal()
})
}
}
group.wait()
這個示例就是每十個任務(wù)并發(fā)執(zhí)行.
測試六: 使用DispatchWorkItem
print("當(dāng)前線程: \(Thread.current)")
// 新建一個任務(wù)
let workItem = DispatchWorkItem {
print("執(zhí)行一個任務(wù)\(Thread.current)")
sleep(3)
}
// 在當(dāng)前線程執(zhí)行任務(wù)
workItem.perform()
// 執(zhí)行完成后, 通知主隊(duì)列
workItem.notify(queue: DispatchQueue.main) {
print("任務(wù)完成了")
}
輸出:
當(dāng)前線程: <NSThread: 0x6000000774c0>{number = 1, name = main}
執(zhí)行一個任務(wù)<NSThread: 0x6000000774c0>{number = 1, name = main}
任務(wù)完成了
在另一個隊(duì)列執(zhí)行任務(wù)(異步):
print("當(dāng)前線程: \(Thread.current)")
// 新建一個任務(wù)
let workItem = DispatchWorkItem {
print("執(zhí)行一個任務(wù)\(Thread.current)")
sleep(3)
}
let queue = DispatchQueue.global()
// 執(zhí)行任務(wù)
queue.async(execute: workItem)
// 執(zhí)行完成后, 通知主隊(duì)列
workItem.notify(queue: DispatchQueue.main) {
print("任務(wù)完成了")
}
print("任務(wù)結(jié)束")
輸出:
當(dāng)前線程: <NSThread: 0x608000261700>{number = 1, name = main}
任務(wù)結(jié)束
執(zhí)行一個任務(wù)<NSThread: 0x6080004661c0>{number = 4, name = (null)}
任務(wù)完成了
在創(chuàng)建任務(wù)的時候, 可使用下面的參數(shù)來設(shè)置其優(yōu)先級:
let workItem = DispatchWorkItem(qos: <#T##DispatchQoS#>, block: <#T##() -> Void#>)
4. 一些應(yīng)用
4.1延遲執(zhí)行
print("當(dāng)前線程: \(Thread.current)")
print("開始時間\(Date())")
let delayQueue = DispatchQueue(label: "delayQueue")
// 延遲 2s
let delayTime = DispatchTimeInterval.seconds(2)
delayQueue.asyncAfter(deadline: .now() + delayTime) {
print("這是延遲2s后執(zhí)行的任務(wù), 結(jié)束時間\(Date())")
}
輸出:
當(dāng)前線程: <NSThread: 0x608000077d80>{number = 1, name = main}
開始時間2017-05-17 06:52:03 +0000
任務(wù)結(jié)束
這是延遲2s后執(zhí)行的任務(wù), 結(jié)束時間2017-05-17 06:52:05 +0000
這里的時間有以下幾種方式設(shè)置:
let delayTime = DispatchTimeInterval.seconds(2)// 秒
let delayTime = DispatchTimeInterval.milliseconds(2*1000)// 毫秒
let delayTime = DispatchTimeInterval.microseconds(2*1000*1000)// 微秒
let delayTime = DispatchTimeInterval.nanoseconds(2*1000*1000*1000)// 納秒
這里都是設(shè)置的延遲2s
也可以如下, 直接加上要延遲的時間間隔:
print("當(dāng)前線程: \(Thread.current)")
print("開始時間\(Date())")
let delayQueue = DispatchQueue(label: "delayQueue")
// 延遲 2s
delayQueue.asyncAfter(deadline: .now() + 2) {
print("這是延遲2s后執(zhí)行的任務(wù), 結(jié)束時間\(Date())")
}
print("任務(wù)結(jié)束")
4.2 匯總執(zhí)行
如果, 你想某個任務(wù)在其他任務(wù)執(zhí)行之后再執(zhí)行, 或者必須某個任務(wù)執(zhí)行完,才能執(zhí)行下面的任務(wù), 可以使用DispatchGroup:
print("當(dāng)前線程: \(Thread.current)")
let queue = DispatchQueue(label: "queueName", attributes: .concurrent)
queue.async {
sleep(4)
print("任務(wù) 1")
}
queue.async {
sleep(2)
print("任務(wù) 2")
}
queue.async {
sleep(6)
print("任務(wù) 3")
}
DispatchGroup.init().notify(qos: .default, flags: .barrier, queue: queue) {
print("所有任務(wù)結(jié)束")
}
print("任務(wù)結(jié)束")