Swift 5.1 - GCD使用總結(jié)

在swift中GCD采用鏈?zhǔn)秸{(diào)用胎源,較OC而言使用方式更為簡單局扶,可讀性更高就谜。全文代碼均默認(rèn)在主線程中執(zhí)行舷胜。

隊(duì)列的獲取與創(chuàng)建

        //串行隊(duì)列
        let serial = DispatchQueue(label: "serial",attributes: .init(rawValue:0))
        //并發(fā)隊(duì)列
        let concurrent = DispatchQueue(label: "serial",attributes: .concurrent)
        //主隊(duì)列
        let mainQueue = DispatchQueue.main
        //全局隊(duì)列
        let global = DispatchQueue.global()

GCD隊(duì)列都遵循先進(jìn)先出(FIFO)娩践。所以往并發(fā)隊(duì)列中添加同步任務(wù),其執(zhí)行順序和任務(wù)的添加順序相同烹骨。全局隊(duì)列在功能上和并發(fā)隊(duì)列是等價的翻伺,所以需要并發(fā)隊(duì)列時,首選使用系統(tǒng)的全局隊(duì)列沮焕。

這里要注意一點(diǎn)并發(fā)隊(duì)列不能稱為并行隊(duì)列吨岭。請參考并發(fā)和并行的區(qū)別

同步任務(wù)與異步任務(wù)

主隊(duì)列+同步任務(wù)——死鎖

同步任務(wù)會阻塞線程峦树,在如下代碼中需要優(yōu)先執(zhí)行print(2)辣辫,等待其執(zhí)行后才能繼續(xù)往下簿废,但是主隊(duì)列為串行隊(duì)列,需要等待當(dāng)前任務(wù)執(zhí)行完成后才能執(zhí)行后加入隊(duì)列的print(2)任務(wù)络它,造成相互等待族檬。

        print(1)
        DispatchQueue.main.sync {
            print(2)
        }
        print(3)
主隊(duì)列+異步任務(wù)——依次執(zhí)行(不開啟新線程)

以下代碼中的print(2)任務(wù)會添加到主隊(duì)列的最后。又由于主線程+異步任務(wù)不會開啟新線程化戳,以下代碼輸出1 3 2单料,順序固定不變。

        print(1)
        DispatchQueue.main.async {
            print(2)
        }
        print(3)
串行隊(duì)列+同步任務(wù)——依次執(zhí)行

以下代碼不管每個任務(wù)休眠時間多長点楼,輸出順序始終為0...10

        //串行隊(duì)列
        let serial = DispatchQueue(label: "serial",attributes: .init(rawValue:0))
        for i in 0...10 {
            serial.sync {
                sleep(arc4random()%3)//休眠時間隨機(jī)
                print(i)
            }
        }
串行隊(duì)列+異步任務(wù)——開啟一個新線程依次執(zhí)行

以下代碼輸出順序始終為0...10扫尖,并且for循環(huán)中的Thread.current的輸出始終為同一個新線程

        //串行隊(duì)列
        let serial = DispatchQueue(label: "serial",attributes: .init(rawValue:0))
        print(Thread.current)//主線程
        for i in 0...10 {
            serial.async {
                sleep(arc4random()%3)//休眠時間隨機(jī)
                print(i,Thread.current)//子線程
            }
        }
并發(fā)隊(duì)列+同步任務(wù)——依次執(zhí)行

以下代碼輸出順序始終為0...10,且線程始終為主線程

        for i in 0...10 {
            DispatchQueue.global().sync {
                sleep(arc4random()%3)//休眠時間隨機(jī)
                print(i,Thread.current)
            }
        }
并發(fā)隊(duì)列+異步任務(wù)——開啟多個線程并發(fā)執(zhí)行

以下代碼輸出順序隨機(jī),且線程信息不同掠廓。注意:這里可能不會輸出11個不同的線程信息换怖,經(jīng)過代碼測試發(fā)現(xiàn)當(dāng)一個線程的任務(wù)執(zhí)行完成后,如果隊(duì)列中還有任務(wù)蟀瞧,此線程會繼續(xù)被調(diào)度執(zhí)行后續(xù)任務(wù)沉颂。 將任務(wù)數(shù)增多,結(jié)果更明顯悦污。

        for i in 0...10 {
            DispatchQueue.global().async {
                sleep(arc4random()%3)//休眠時間隨機(jī)
                print(i,Thread.current)
            }
        }
并發(fā)隊(duì)列——最大并發(fā)數(shù)

GCD并不能無限制的創(chuàng)建線程铸屉,如下代碼其實(shí)最多創(chuàng)建64個子線程,意味著最大并發(fā)數(shù)為64切端。參考

       for i in 0...1000 {
            DispatchQueue.global().async {
                print(i,Thread.current)
                sleep(10000)
            }
        }
GCD 柵欄

在swift中柵欄不再是一個單獨(dú)的方法彻坛。而是DispatchWorkItemFlags結(jié)構(gòu)體中的一個屬性。sync/async方法的其中一個參數(shù)類型即為DispatchWorkItemFlags踏枣,所以使用代碼如下昌屉。這樣的調(diào)用方式可以更好的理解柵欄,其實(shí)它就是一個分隔任務(wù)茵瀑,將其添加到需要柵欄的隊(duì)列中间驮,以分隔添加前后的其他任務(wù)。以下代碼柵欄前后均為并發(fā)執(zhí)行瘾婿。如果將添加?xùn)艡谛薷臑?code>sync則會阻塞當(dāng)前線程蜻牢。

        for i in 0...10 {
            DispatchQueue.global().async {
                print(i)
            }
        }
        DispatchQueue.global().async(flags: .barrier) {
            print("this is barrier")
        }
        for i in 11...20 {
            DispatchQueue.global().async {
                print(i)
            }
        }
GCD group

隊(duì)列組一般用來處理任務(wù)的依賴,比如需要等待多個網(wǎng)絡(luò)請求返回后才能繼續(xù)執(zhí)行后續(xù)任務(wù)偏陪。

使用notify添加結(jié)束任務(wù)

必須要等待group中的任務(wù)執(zhí)行完成后才能執(zhí)行抢呆,無法定義超時。

    override func viewDidLoad() {
        let group = DispatchGroup()
        for i in 0...10 {
            DispatchQueue.global().async(group: group) {
                sleep(arc4random()%3)//休眠時間隨機(jī)
                print(i)
            }
        }
        //queue參數(shù)表示以下任務(wù)添加到的隊(duì)列
        group.notify(queue: DispatchQueue.main) {
            print("group 任務(wù)執(zhí)行結(jié)束")
        }
    }
使用wait進(jìn)行等待——可定義超時
        let group = DispatchGroup()
        for i in 0...10 {
            DispatchQueue.global().async(group: group) {
                sleep(arc4random()%10)//休眠時間隨機(jī)
                print(i)
            }
        }
        switch group.wait(timeout: DispatchTime.now()+5) {
        case .success:
            print("group 任務(wù)執(zhí)行結(jié)束")
        case .timedOut:
            print("group 任務(wù)執(zhí)行超時")
        }
enter()leave()

enter()是標(biāo)示一個任務(wù)加入到隊(duì)列笛谦,leave()標(biāo)識一個隊(duì)列中的任務(wù)執(zhí)行完成抱虐。
注意:enter()leave()必須成對調(diào)用
如果enter()后未調(diào)用leave()則不會觸發(fā)notify饥脑,wait也只會timeout恳邀。
如果leave()之前沒有調(diào)用enter()則會引起crash懦冰。

信號量 semaphore

GCD 中的信號量是指 Dispatch Semaphore,是持有計(jì)數(shù)的信號谣沸。類似于過高速路收費(fèi)站的欄桿刷钢。可以通過時乳附,打開欄桿内地,不可以通過時,關(guān)閉欄桿赋除。在 Dispatch Semaphore 中阱缓,使用計(jì)數(shù)來完成這個功能,計(jì)數(shù)為0時等待举农,不可通過荆针。計(jì)數(shù)為1或大于1時,計(jì)數(shù)減1且不等待颁糟,可通過航背。

        let semaphore = DispatchSemaphore(value: 0)//創(chuàng)建一個信號量,并初始化信號總量
        semaphore.signal()//發(fā)送一個信號讓信號量加1
        semaphore.wait()//可以使總信號量減1滚停,當(dāng)信號總量為0時就會一直等待(阻塞所在線程)沃粗,否則就可以正常執(zhí)行。
信號量處理線程同步

將異步執(zhí)行任務(wù)轉(zhuǎn)化為同步執(zhí)行任務(wù)键畴。如必須等待異步的網(wǎng)絡(luò)請求返回后才能執(zhí)行后續(xù)任務(wù)時。

        let semaphore = DispatchSemaphore(value: 0)
        DispatchQueue.global().async {
            sleep(arc4random()%5)//休眠時間隨機(jī)
            print("completed")
            semaphore.signal()
        }
        switch semaphore.wait(timeout: DispatchTime.now()+10) {//信號量為0突雪,調(diào)用wait后阻塞線程
        case .success:
            print("success")
        case .timedOut:
            print("timeout")
        }
        print("over")
信號量控制最大并發(fā)數(shù)

在Operation中可以通過maxConcurrentOperationCount輕松實(shí)現(xiàn)控制最大并發(fā)數(shù)起惕,GCD中需要借助信號量實(shí)現(xiàn)。以下代碼就限制了最多兩個任務(wù)并發(fā)執(zhí)行咏删。

        let semaphore = DispatchSemaphore(value: 2)
        for i in 0...10 {
            semaphore.wait()//當(dāng)信號量為0時惹想,阻塞在此
            DispatchQueue.global().async {
                sleep(3)
                print(i,Thread.current)
                semaphore.signal()//信號量加1
            }
            print("=======================")
        }
使用DispatchSemaphore加鎖

非線程安全,即當(dāng)一個變量可能同時被多個線程修改督函。以下代碼如果不使用信號量輸出是隨機(jī)值嘀粱。

        let semaphore = DispatchSemaphore(value: 1)
        var i = 0
        for _ in 1...10 {
            DispatchQueue.global().async {
                semaphore.wait()//當(dāng)信號量為0時,阻塞在此
                for _ in 1...10 {
                    i += 1
                }
                print(i)
                semaphore.signal()//信號量加1
            }
        }
延時任務(wù)

使用GCD執(zhí)行延時任務(wù)指定的并不是任務(wù)的執(zhí)行時間辰狡,而是加入隊(duì)列的時間锋叨。所以執(zhí)行時間可能不太精確。但是任務(wù)是通過閉包加入宛篇,相較performSelectorAfterDelay可讀性更好娃磺,也更安全。

        DispatchQueue.global().asyncAfter(deadline: DispatchTime.now()+2) {
            print("延時任務(wù)")
        }
dispatch_once

在swift3以后dispatch_once被廢棄叫倍。swift中有更好的定義單例和一次性代碼的方式偷卧。

DispatchWorkItem與任務(wù)取消

DispatchWorkItem其實(shí)就是用來代替OC中的dispatch_block_t豺瘤。如果任務(wù)是通過DispatchWorkItem定義。在執(zhí)行之前听诸,可以執(zhí)行取消操作坐求。注意即使任務(wù)已經(jīng)加入隊(duì)列,只要還未執(zhí)行就可以進(jìn)行取消晌梨,但是無法判斷任務(wù)在隊(duì)列中的狀態(tài)瞻赶,所以一般會根據(jù)加入隊(duì)列的時間確定是否可以取消。

        let workItem = DispatchWorkItem {
            print("延時任務(wù)")
        }
        DispatchQueue.global().asyncAfter(deadline: DispatchTime.now()+2, execute: workItem)
        sleep(1)
        workItem.cancel()
DispatchWorkItem主動執(zhí)行
        let workItem = DispatchWorkItem {
            print("workItem")
        }
        workItem.perform()
等待DispatchWorkItem執(zhí)行完成
        let workItem = DispatchWorkItem {
            sleep(3)
            print("workItem")
        }
        DispatchQueue.global().async(execute: workItem)
        switch workItem.wait(timeout: DispatchTime.now()+5) {
        case .success:
            print("success")
        case .timedOut:
            print("timeout")
        }
DispatchWorkItem執(zhí)行完成通知
        let workItem = DispatchWorkItem {
            sleep(3)
            print("workItem")
        }
        DispatchQueue.global().async(execute: workItem)
        workItem.notify(queue: DispatchQueue.main) {
            print("completed")
        }

參考:

GCD最大線程數(shù)

iOS 多線程:『GCD』詳盡總結(jié)

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末派任,一起剝皮案震驚了整個濱河市砸逊,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌掌逛,老刑警劉巖师逸,帶你破解...
    沈念sama閱讀 206,602評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異豆混,居然都是意外死亡篓像,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,442評論 2 382
  • 文/潘曉璐 我一進(jìn)店門皿伺,熙熙樓的掌柜王于貴愁眉苦臉地迎上來员辩,“玉大人,你說我怎么就攤上這事鸵鸥〉旎” “怎么了?”我有些...
    開封第一講書人閱讀 152,878評論 0 344
  • 文/不壞的土叔 我叫張陵妒穴,是天一觀的道長宋税。 經(jīng)常有香客問我,道長讼油,這世上最難降的妖魔是什么杰赛? 我笑而不...
    開封第一講書人閱讀 55,306評論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮矮台,結(jié)果婚禮上乏屯,老公的妹妹穿的比我還像新娘。我一直安慰自己瘦赫,他們只是感情好辰晕,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,330評論 5 373
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著耸彪,像睡著了一般伞芹。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,071評論 1 285
  • 那天唱较,我揣著相機(jī)與錄音扎唾,去河邊找鬼。 笑死南缓,一個胖子當(dāng)著我的面吹牛胸遇,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播汉形,決...
    沈念sama閱讀 38,382評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼纸镊,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了概疆?” 一聲冷哼從身側(cè)響起逗威,我...
    開封第一講書人閱讀 37,006評論 0 259
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎岔冀,沒想到半個月后凯旭,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,512評論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡使套,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,965評論 2 325
  • 正文 我和宋清朗相戀三年罐呼,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片侦高。...
    茶點(diǎn)故事閱讀 38,094評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡嫉柴,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出奉呛,到底是詐尸還是另有隱情计螺,我是刑警寧澤,帶...
    沈念sama閱讀 33,732評論 4 323
  • 正文 年R本政府宣布侧馅,位于F島的核電站危尿,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏馁痴。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,283評論 3 307
  • 文/蒙蒙 一肺孤、第九天 我趴在偏房一處隱蔽的房頂上張望罗晕。 院中可真熱鬧,春花似錦赠堵、人聲如沸小渊。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,286評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽酬屉。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間呐萨,已是汗流浹背杀饵。 一陣腳步聲響...
    開封第一講書人閱讀 31,512評論 1 262
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留谬擦,地道東北人切距。 一個月前我還...
    沈念sama閱讀 45,536評論 2 354
  • 正文 我出身青樓,卻偏偏與公主長得像惨远,于是被迫代替她去往敵國和親谜悟。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,828評論 2 345

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