[Swift]多線程--GCD

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é)束")
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末挤庇,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子院领,更是在濱河造成了極大的恐慌翠胰,老刑警劉巖睹酌,帶你破解...
    沈念sama閱讀 222,000評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件侯养,死亡現(xiàn)場離奇詭異,居然都是意外死亡鲸鹦,警方通過查閱死者的電腦和手機(jī)慧库,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,745評論 3 399
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來馋嗜,“玉大人齐板,你說我怎么就攤上這事。” “怎么了覆积?”我有些...
    開封第一講書人閱讀 168,561評論 0 360
  • 文/不壞的土叔 我叫張陵听皿,是天一觀的道長。 經(jīng)常有香客問我宽档,道長,這世上最難降的妖魔是什么庵朝? 我笑而不...
    開封第一講書人閱讀 59,782評論 1 298
  • 正文 為了忘掉前任吗冤,我火速辦了婚禮,結(jié)果婚禮上九府,老公的妹妹穿的比我還像新娘椎瘟。我一直安慰自己,他們只是感情好侄旬,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,798評論 6 397
  • 文/花漫 我一把揭開白布肺蔚。 她就那樣靜靜地躺著,像睡著了一般儡羔。 火紅的嫁衣襯著肌膚如雪宣羊。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,394評論 1 310
  • 那天汰蜘,我揣著相機(jī)與錄音仇冯,去河邊找鬼。 笑死族操,一個胖子當(dāng)著我的面吹牛苛坚,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播色难,決...
    沈念sama閱讀 40,952評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼泼舱,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了枷莉?” 一聲冷哼從身側(cè)響起娇昙,我...
    開封第一講書人閱讀 39,852評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎依沮,沒想到半個月后涯贞,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,409評論 1 318
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡危喉,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,483評論 3 341
  • 正文 我和宋清朗相戀三年宋渔,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片辜限。...
    茶點(diǎn)故事閱讀 40,615評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡皇拣,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情氧急,我是刑警寧澤颗胡,帶...
    沈念sama閱讀 36,303評論 5 350
  • 正文 年R本政府宣布,位于F島的核電站吩坝,受9級特大地震影響毒姨,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜钉寝,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,979評論 3 334
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望嵌纲。 院中可真熱鬧俘枫,春花似錦、人聲如沸逮走。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,470評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽师溅。三九已至茅信,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間险胰,已是汗流浹背汹押。 一陣腳步聲響...
    開封第一講書人閱讀 33,571評論 1 272
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留起便,地道東北人棚贾。 一個月前我還...
    沈念sama閱讀 49,041評論 3 377
  • 正文 我出身青樓,卻偏偏與公主長得像榆综,于是被迫代替她去往敵國和親妙痹。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,630評論 2 359

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