[Swift]多線程--GCD

1. 前言

之前寫了一遍文章介紹了Objective-C中GCD的用法: [iOS]多線程--GCD, 現(xiàn)在轉(zhuǎn)戰(zhàn)到Swift的戰(zhàn)場, 根據(jù)前者一些知識(shí)點(diǎn), 講下使用Swift如何實(shí)現(xiàn).

2. 隊(duì)列

2.1 串行隊(duì)列

創(chuàng)建串行隊(duì)列的方式很簡單:

DispatchQueue(label: <#T##String#>)
  • label : 隊(duì)列的標(biāo)識(shí)符

例如:

let queue = DispatchQueue(label: "QueueIdentifier")

如果想指定串行隊(duì)列的優(yōu)先級(jí), 可使用下面的方法來創(chuàng)建:

let queue = DispatchQueue(label: "QueueIdentifier", qos: .userInitiated)

參數(shù)qos: 用于指定隊(duì)列的優(yōu)先級(jí), 是個(gè)枚舉:

  • .userInteractive
  • .userInitiated
  • .default
  • .utility
  • .background
  • .unspecified
    優(yōu)先級(jí), 由上往下依次降低

一個(gè)最常用的串行隊(duì)列--主隊(duì)列:

DispatchQueue.main

2.2 并行隊(duì)列

并行隊(duì)列, 可以使用下面這個(gè)方法進(jìn)行創(chuàng)建:

DispatchQueue(label: <#T##String#>, qos: <#T##DispatchQoS#>, attributes: <#T##DispatchQueue.Attributes#>)

和創(chuàng)建串行隊(duì)列的方法一樣, 只不過多了一個(gè)參數(shù)attributes, 傳 .concurrent 即可創(chuàng)建一個(gè)并行隊(duì)列

let queue1 = DispatchQueue(label: "并行隊(duì)列的創(chuàng)建", qos: .default, attributes: .concurrent)

在使用的時(shí)候, 我們一般不去創(chuàng)建并行隊(duì)列, 而是使用系統(tǒng)為我們提供的全局的并行隊(duì)列:

// 獲取全局并行隊(duì)列
let queue = DispatchQueue.global()

在獲取的時(shí)候我們也可以指定其優(yōu)先級(jí):

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

注意:
在創(chuàng)建串行并行隊(duì)列的時(shí)候, 參數(shù)attributes, 可以指定創(chuàng)建的是串行還是并行隊(duì)列, 他還有一個(gè)值: .initiallyInactive, 即: 創(chuàng)建的時(shí)候, 是處于不活躍狀態(tài), 即不會(huì)執(zhí)行任務(wù), 需要手動(dòng)調(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é)束")

這個(gè)示例, 程序會(huì)crash, 應(yīng)該這樣:

print("當(dāng)前線程: \(Thread.current)")
        let queue = DispatchQueue(label: "開始不活躍的串行隊(duì)列", attributes: .initiallyInactive)
            queue.async {
            print("執(zhí)行了么?")
        }
        queue.activate()
        print("任務(wù)結(jié)束")

上面這個(gè)是, 串行的不活躍隊(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í)例對(duì)象調(diào)用sync(同步)/async(異步)方法即可, 這里可以使用閉包, 也可以使用DispatchWorkItem對(duì)象

queue.sync {
            <#code#>
        }
queue.async(execute: <#T##DispatchWorkItem#>)

3. 同步, 異步, 串行, 并發(fā)組合測試

測試一: 用同步函數(shù)往串行隊(duì)列中添加任務(wù)

不會(huì)開啟新的線程:

print("當(dāng)前線程: \(Thread.current)")
       
        let queue = DispatchQueue(label: "創(chuàng)建串行隊(duì)列")
        
        queue.sync {
            print("串行隊(duì)列中同步執(zhí)行的第1個(gè)任務(wù): \(Thread.current)")
            sleep(4)
        }
        
        queue.sync {
            print("串行隊(duì)列中同步執(zhí)行的第2個(gè)任務(wù): \(Thread.current)")
            sleep(2)
        }
        
        queue.sync {
            print("串行隊(duì)列中同步執(zhí)行的第3個(gè)任務(wù): \(Thread.current)")
        }

控制臺(tái)輸出:

當(dāng)前線程: <NSThread: 0x600000065900>{number = 1, name = main}
串行隊(duì)列中同步執(zhí)行的第1個(gè)任務(wù): <NSThread: 0x600000065900>{number = 1, name = main}
串行隊(duì)列中同步執(zhí)行的第2個(gè)任務(wù): <NSThread: 0x600000065900>{number = 1, name = main}
串行隊(duì)列中同步執(zhí)行的第3個(gè)任務(wù): <NSThread: 0x600000065900>{number = 1, name = main}

可以看出, 沒有開啟新的線程, 同時(shí)也是按照順序依次執(zhí)行的;

測試二: 用異步函數(shù)往串行隊(duì)列中添加任務(wù)

會(huì)開啟線程,但是只開啟一個(gè)線程:

print("當(dāng)前線程: \(Thread.current)")
        
        
        let queue = DispatchQueue(label: "創(chuàng)建串行隊(duì)列")
        
        queue.async {
            print("串行隊(duì)列中同步執(zhí)行的第1個(gè)任務(wù): \(Thread.current)")
            sleep(4)
        }
        
        queue.async {
            print("串行隊(duì)列中同步執(zhí)行的第2個(gè)任務(wù): \(Thread.current)")
            sleep(2)
        }
        
        queue.async {
            print("串行隊(duì)列中同步執(zhí)行的第3個(gè)任務(wù): \(Thread.current)")
        }

控制臺(tái)輸出:

當(dāng)前線程: <NSThread: 0x608000064000>{number = 1, name = main}
串行隊(duì)列中同步執(zhí)行的第1個(gè)任務(wù): <NSThread: 0x608000071dc0>{number = 4, name = (null)}
串行隊(duì)列中同步執(zhí)行的第2個(gè)任務(wù): <NSThread: 0x608000071dc0>{number = 4, name = (null)}
串行隊(duì)列中同步執(zhí)行的第3個(gè)任務(wù): <NSThread: 0x608000071dc0>{number = 4, name = (null)}

雖然是異步函數(shù), 但是添加到了串行隊(duì)列里, 只開啟了一個(gè)新的線程, 添加到其中的任務(wù)還是按順序依次執(zhí)行的

測試三: 用同步函數(shù)往并發(fā)隊(duì)列中添加任務(wù)

不會(huì)開啟新的線程((同步函數(shù)不具備開啟新線程的能力)),并發(fā)隊(duì)列失去了并發(fā)的功能:

 print("當(dāng)前線程: \(Thread.current)")
        
        
        let queue = DispatchQueue(label: "創(chuàng)建并行隊(duì)列", attributes: .concurrent)
        
        queue.sync {
            print("串行隊(duì)列中同步執(zhí)行的第1個(gè)任務(wù): \(Thread.current)")
            sleep(4)
        }
        
        queue.sync {
            print("串行隊(duì)列中同步執(zhí)行的第2個(gè)任務(wù): \(Thread.current)")
            sleep(2)
        }
        
        queue.sync {
            print("串行隊(duì)列中同步執(zhí)行的第3個(gè)任務(wù): \(Thread.current)")
        }

控制臺(tái)輸出:

當(dāng)前線程: <NSThread: 0x600000077b80>{number = 1, name = main}
串行隊(duì)列中同步執(zhí)行的第1個(gè)任務(wù): <NSThread: 0x600000077b80>{number = 1, name = main}
串行隊(duì)列中同步執(zhí)行的第2個(gè)任務(wù): <NSThread: 0x600000077b80>{number = 1, name = main}
串行隊(duì)列中同步執(zhí)行的第3個(gè)任務(wù): <NSThread: 0x600000077b80>{number = 1, name = main}

可以看出, 雖然使用的是并發(fā)隊(duì)列, 但是使用的是同步函數(shù), 由于同步函數(shù)沒有開啟新線程的能力, 所以并發(fā)隊(duì)列就失去了并發(fā)性, 按照任務(wù)的添加順序, 順序執(zhí)行;

測試四. 用異步函數(shù)往并發(fā)隊(duì)列中添加任務(wù)

同時(shí)開啟多個(gè)子線程執(zhí)行任務(wù):

 print("當(dāng)前線程: \(Thread.current)")
        
        
        let queue = DispatchQueue(label: "創(chuàng)建并行隊(duì)列", attributes: .concurrent)
        
        queue.async {
            print("串行隊(duì)列中同步執(zhí)行的第1個(gè)任務(wù): \(Thread.current)")
            sleep(4)
        }
        
        queue.async {
            print("串行隊(duì)列中同步執(zhí)行的第2個(gè)任務(wù): \(Thread.current)")
            sleep(2)
        }
        
        queue.async {
            print("串行隊(duì)列中同步執(zhí)行的第3個(gè)任務(wù): \(Thread.current)")
        }

控制臺(tái)輸出:

當(dāng)前線程: <NSThread: 0x60000007c6c0>{number = 1, name = main}
串行隊(duì)列中同步執(zhí)行的第2個(gè)任務(wù): <NSThread: 0x600000270380>{number = 4, name = (null)}
串行隊(duì)列中同步執(zhí)行的第3個(gè)任務(wù): <NSThread: 0x60800027dec0>{number = 5, name = (null)}
串行隊(duì)列中同步執(zhí)行的第1個(gè)任務(wù): <NSThread: 0x60000026b600>{number = 3, name = (null)}

可以看出, 這里開啟了三個(gè)子線程來執(zhí)行任務(wù), 互相之間沒有影響.
只有在并發(fā)隊(duì)列異步執(zhí)行的時(shí)候才能真正起到 并發(fā)的作用.

測試五. 控制最大并發(fā)數(shù)

在進(jìn)行并發(fā)操作的時(shí)候, 如果任務(wù)過多, 開啟很多線程, 會(huì)導(dǎo)致APP卡死. 所以, 我們要控制最大并發(fā)數(shù), 這就用到了信號(hào)量DispatchSemaphore, 我們可以這樣創(chuàng)建一個(gè)信號(hào)量:

let semaphore = DispatchSemaphore.init(value: 10)

參數(shù)為最大并發(fā)執(zhí)行的任務(wù)數(shù), 也即是信號(hào)量.
信號(hào)量減一:

semaphore.wait()
// 如果信號(hào)量大于1, 則會(huì)繼續(xù)執(zhí)行, 如果信號(hào)量等于0, 會(huì)等待timeout的時(shí)間, 在等待期間被semaphore.signal()加一了, 這里會(huì)繼續(xù)執(zhí)行, 并將信號(hào)量減一
semaphore.wait(timeout: <#T##DispatchTime#>)

上面函數(shù)的返回值為DispatchTimeoutResult, 是個(gè)枚舉:

  • success
  • timedOut

DispatchTime的值可使用: .now() , 或者 .distantFuture
也可以創(chuàng)建:

DispatchTime.init(uptimeNanoseconds: <#T##UInt64#>)

這里的參數(shù)單位為納秒: 1s = 1000*1000*100ns

信號(hào)量加一:

semaphore.signal()

一個(gè)應(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ù)時(shí)間
                    sleep(2)
                    // 任務(wù)結(jié)束, 信號(hào)量+1
                    semaphore.signal()
                })
            }
            
        }
        
        group.wait()

這個(gè)示例就是每十個(gè)任務(wù)并發(fā)執(zhí)行.

測試六: 使用DispatchWorkItem
print("當(dāng)前線程: \(Thread.current)")
        
        // 新建一個(gè)任務(wù)
        let workItem = DispatchWorkItem { 
            
            print("執(zhí)行一個(gè)任務(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í)行一個(gè)任務(wù)<NSThread: 0x6000000774c0>{number = 1, name = main}
任務(wù)完成了

在另一個(gè)隊(duì)列執(zhí)行任務(wù)(異步):

 print("當(dāng)前線程: \(Thread.current)")
        
        // 新建一個(gè)任務(wù)
        let workItem = DispatchWorkItem { 
            
            print("執(zhí)行一個(gè)任務(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í)行一個(gè)任務(wù)<NSThread: 0x6080004661c0>{number = 4, name = (null)}
任務(wù)完成了

在創(chuàng)建任務(wù)的時(shí)候, 可使用下面的參數(shù)來設(shè)置其優(yōu)先級(jí):

let workItem = DispatchWorkItem(qos: <#T##DispatchQoS#>, block: <#T##() -> Void#>)

4. 一些應(yīng)用

4.1延遲執(zhí)行
print("當(dāng)前線程: \(Thread.current)")
        print("開始時(shí)間\(Date())")
        let delayQueue = DispatchQueue(label: "delayQueue")
        // 延遲 2s
        let delayTime = DispatchTimeInterval.seconds(2)
        
        delayQueue.asyncAfter(deadline: .now() + delayTime) {
            print("這是延遲2s后執(zhí)行的任務(wù), 結(jié)束時(shí)間\(Date())")
            
        }

輸出:

當(dāng)前線程: <NSThread: 0x608000077d80>{number = 1, name = main}
開始時(shí)間2017-05-17 06:52:03 +0000
任務(wù)結(jié)束
這是延遲2s后執(zhí)行的任務(wù), 結(jié)束時(shí)間2017-05-17 06:52:05 +0000

這里的時(shí)間有以下幾種方式設(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
也可以如下, 直接加上要延遲的時(shí)間間隔:

print("當(dāng)前線程: \(Thread.current)")
        print("開始時(shí)間\(Date())")
        let delayQueue = DispatchQueue(label: "delayQueue")
        // 延遲 2s
        delayQueue.asyncAfter(deadline: .now() + 2) {
            print("這是延遲2s后執(zhí)行的任務(wù), 結(jié)束時(shí)間\(Date())")
        }
        print("任務(wù)結(jié)束")
4.2 匯總執(zhí)行

如果, 你想某個(gè)任務(wù)在其他任務(wù)執(zhí)行之后再執(zhí)行, 或者必須某個(gè)任務(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é)束")
最后編輯于
?著作權(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),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

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

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