Swift 5.1 - GCD使用总结

在swift中GCD采用链式调用,较OC而言使用方式更为简单,可读性更高。全文代码均默认在主线程中执行。

队列的获取与创建

        //串行队列
        let serial = DispatchQueue(label: "serial",attributes: .init(rawValue:0))
        //并发队列
        let concurrent = DispatchQueue(label: "serial",attributes: .concurrent)
        //主队列
        let mainQueue = DispatchQueue.main
        //全局队列
        let global = DispatchQueue.global()

GCD队列都遵循先进先出(FIFO)。所以往并发队列中添加同步任务,其执行顺序和任务的添加顺序相同。全局队列在功能上和并发队列是等价的,所以需要并发队列时,首选使用系统的全局队列。

这里要注意一点并发队列不能称为并行队列。请参考并发和并行的区别

同步任务与异步任务

主队列+同步任务——死锁

同步任务会阻塞线程,在如下代码中需要优先执行print(2),等待其执行后才能继续往下,但是主队列为串行队列,需要等待当前任务执行完成后才能执行后加入队列的print(2)任务,造成相互等待。

        print(1)
        DispatchQueue.main.sync {
            print(2)
        }
        print(3)
主队列+异步任务——依次执行(不开启新线程)

以下代码中的print(2)任务会添加到主队列的最后。又由于主线程+异步任务不会开启新线程,以下代码输出1 3 2,顺序固定不变。

        print(1)
        DispatchQueue.main.async {
            print(2)
        }
        print(3)
串行队列+同步任务——依次执行

以下代码不管每个任务休眠时间多长,输出顺序始终为0...10

        //串行队列
        let serial = DispatchQueue(label: "serial",attributes: .init(rawValue:0))
        for i in 0...10 {
            serial.sync {
                sleep(arc4random()%3)//休眠时间随机
                print(i)
            }
        }
串行队列+异步任务——开启一个新线程依次执行

以下代码输出顺序始终为0...10,并且for循环中的Thread.current的输出始终为同一个新线程

        //串行队列
        let serial = DispatchQueue(label: "serial",attributes: .init(rawValue:0))
        print(Thread.current)//主线程
        for i in 0...10 {
            serial.async {
                sleep(arc4random()%3)//休眠时间随机
                print(i,Thread.current)//子线程
            }
        }
并发队列+同步任务——依次执行

以下代码输出顺序始终为0...10,且线程始终为主线程

        for i in 0...10 {
            DispatchQueue.global().sync {
                sleep(arc4random()%3)//休眠时间随机
                print(i,Thread.current)
            }
        }
并发队列+异步任务——开启多个线程并发执行

以下代码输出顺序随机,且线程信息不同。注意:这里可能不会输出11个不同的线程信息,经过代码测试发现当一个线程的任务执行完成后,如果队列中还有任务,此线程会继续被调度执行后续任务。 将任务数增多,结果更明显。

        for i in 0...10 {
            DispatchQueue.global().async {
                sleep(arc4random()%3)//休眠时间随机
                print(i,Thread.current)
            }
        }
并发队列——最大并发数

GCD并不能无限制的创建线程,如下代码其实最多创建64个子线程,意味着最大并发数为64。参考

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

在swift中栅栏不再是一个单独的方法。而是DispatchWorkItemFlags结构体中的一个属性。sync/async方法的其中一个参数类型即为DispatchWorkItemFlags,所以使用代码如下。这样的调用方式可以更好的理解栅栏,其实它就是一个分隔任务,将其添加到需要栅栏的队列中,以分隔添加前后的其他任务。以下代码栅栏前后均为并发执行。如果将添加栅栏修改为sync则会阻塞当前线程。

        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

队列组一般用来处理任务的依赖,比如需要等待多个网络请求返回后才能继续执行后续任务。

使用notify添加结束任务

必须要等待group中的任务执行完成后才能执行,无法定义超时。

    override func viewDidLoad() {
        let group = DispatchGroup()
        for i in 0...10 {
            DispatchQueue.global().async(group: group) {
                sleep(arc4random()%3)//休眠时间随机
                print(i)
            }
        }
        //queue参数表示以下任务添加到的队列
        group.notify(queue: DispatchQueue.main) {
            print("group 任务执行结束")
        }
    }
使用wait进行等待——可定义超时
        let group = DispatchGroup()
        for i in 0...10 {
            DispatchQueue.global().async(group: group) {
                sleep(arc4random()%10)//休眠时间随机
                print(i)
            }
        }
        switch group.wait(timeout: DispatchTime.now()+5) {
        case .success:
            print("group 任务执行结束")
        case .timedOut:
            print("group 任务执行超时")
        }
enter()leave()

enter()是标示一个任务加入到队列,leave()标识一个队列中的任务执行完成。
注意:enter()leave()必须成对调用
如果enter()后未调用leave()则不会触发notify,wait也只会timeout。
如果leave()之前没有调用enter()则会引起crash。

信号量 semaphore

GCD 中的信号量是指 Dispatch Semaphore,是持有计数的信号。类似于过高速路收费站的栏杆。可以通过时,打开栏杆,不可以通过时,关闭栏杆。在 Dispatch Semaphore 中,使用计数来完成这个功能,计数为0时等待,不可通过。计数为1或大于1时,计数减1且不等待,可通过。

        let semaphore = DispatchSemaphore(value: 0)//创建一个信号量,并初始化信号总量
        semaphore.signal()//发送一个信号让信号量加1
        semaphore.wait()//可以使总信号量减1,当信号总量为0时就会一直等待(阻塞所在线程),否则就可以正常执行。
信号量处理线程同步

将异步执行任务转化为同步执行任务。如必须等待异步的网络请求返回后才能执行后续任务时。

        let semaphore = DispatchSemaphore(value: 0)
        DispatchQueue.global().async {
            sleep(arc4random()%5)//休眠时间随机
            print("completed")
            semaphore.signal()
        }
        switch semaphore.wait(timeout: DispatchTime.now()+10) {//信号量为0,调用wait后阻塞线程
        case .success:
            print("success")
        case .timedOut:
            print("timeout")
        }
        print("over")
信号量控制最大并发数

在Operation中可以通过maxConcurrentOperationCount轻松实现控制最大并发数,GCD中需要借助信号量实现。以下代码就限制了最多两个任务并发执行。

        let semaphore = DispatchSemaphore(value: 2)
        for i in 0...10 {
            semaphore.wait()//当信号量为0时,阻塞在此
            DispatchQueue.global().async {
                sleep(3)
                print(i,Thread.current)
                semaphore.signal()//信号量加1
            }
            print("=======================")
        }
使用DispatchSemaphore加锁

非线程安全,即当一个变量可能同时被多个线程修改。以下代码如果不使用信号量输出是随机值。

        let semaphore = DispatchSemaphore(value: 1)
        var i = 0
        for _ in 1...10 {
            DispatchQueue.global().async {
                semaphore.wait()//当信号量为0时,阻塞在此
                for _ in 1...10 {
                    i += 1
                }
                print(i)
                semaphore.signal()//信号量加1
            }
        }
延时任务

使用GCD执行延时任务指定的并不是任务的执行时间,而是加入队列的时间。所以执行时间可能不太精确。但是任务是通过闭包加入,相较performSelectorAfterDelay可读性更好,也更安全。

        DispatchQueue.global().asyncAfter(deadline: DispatchTime.now()+2) {
            print("延时任务")
        }
dispatch_once

在swift3以后dispatch_once被废弃。swift中有更好的定义单例和一次性代码的方式。

DispatchWorkItem与任务取消

DispatchWorkItem其实就是用来代替OC中的dispatch_block_t。如果任务是通过DispatchWorkItem定义。在执行之前,可以执行取消操作。注意即使任务已经加入队列,只要还未执行就可以进行取消,但是无法判断任务在队列中的状态,所以一般会根据加入队列的时间确定是否可以取消。

        let workItem = DispatchWorkItem {
            print("延时任务")
        }
        DispatchQueue.global().asyncAfter(deadline: DispatchTime.now()+2, execute: workItem)
        sleep(1)
        workItem.cancel()
DispatchWorkItem主动执行
        let workItem = DispatchWorkItem {
            print("workItem")
        }
        workItem.perform()
等待DispatchWorkItem执行完成
        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执行完成通知
        let workItem = DispatchWorkItem {
            sleep(3)
            print("workItem")
        }
        DispatchQueue.global().async(execute: workItem)
        workItem.notify(queue: DispatchQueue.main) {
            print("completed")
        }

参考:

GCD最大线程数

iOS 多线程:『GCD』详尽总结

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 193,968评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,682评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,254评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,074评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 60,964评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,055评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,484评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,170评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,433评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,512评论 2 308
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,296评论 1 325
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,184评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,545评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,880评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,150评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,437评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,630评论 2 335

推荐阅读更多精彩内容