GCD

基本概念

什么是 GCD ?

官方文档 说明如下:
Grand Central Dispatch( GCD )是异步启动任务的技术之一。此技术将开发者通常在应用程序中编写的线程管理代码在系统级别中实现。开发者通常要做的是定义要执行的任务并将它们添加到适当的 Dispatch Queue 中。GCD 就能创建所需的线程并计划执行任务。由于线程管理现在是系统的一部分,因此可以统一管理,也可执行任务,这样就比传统线程更有效率。

为什么要用 GCD ?
  • GCD 会自动管理线程的生命周期,无需开发者编写任何线程管理代码
  • GCD 可以通过将昂贵的计算任务放入后台处理,来改善应用的响应性能
  • GCD 提供一个易于使用的并发模型而不仅仅只是锁和线程,以帮助我们避开并发陷阱
  • GCD 具有在常见模式(例如单例)上用更高性能的原语优化你的代码的潜在能力

任务与队列

任务

任务是指需要执行的操作,在 GCD 中就是 block 中的那一段代码。执行任务分为同步(sync) 和异步(async):

  • 同步(sync)
    • 同步添加任务到指定队列中,会一直等待任务执行结束,再执行后续程序
    • 会阻塞线程
    • 不会开启新线程
  • 异步(async)
    • 异步添加任务到指定队列中,不用等待任务执行结束,就可以立即返回执行后续程序
    • 不会阻塞线程
    • 可能会开启新线程
队列(DispatchQueue)

队列是一种特殊的线性表,用于存放任务,采用 FIFO(先进先出)的原则。在 GCD 中队列主要分为串行队列(Serial Dispatch Queue)并行队列(Concurrent Dispatch Queue)两者都符合 FIFO 的原则,主要区别是执行的顺序,以及开启的线程数不同

  • 串行队列(Serial Dispatch Queue):使用一个线程,一个接着一个执行任务
  • 并行队列(Concurrent Dispatch Queue):使用多个线程,同时执行多个任务
    队列

队列与线程

主队列
DispatchQueue(label: "queue").async {
    print("queue: \(Thread.current)")
    DispatchQueue.main.sync {
        print("mainSync: \(Thread.current)")
    }
    DispatchQueue.main.async {
        print("mainAsync1: \(Thread.current)")
    }
    DispatchQueue.main.async {
        print("mainAsync2: \(Thread.current)")
    }
}

打印:
queue: <NSThread: 0x6000031c1580>{number = 3, name = (null)}
mainSync: <NSThread: 0x6000031920c0>{number = 1, name = main}
mainAsync1: <NSThread: 0x6000031920c0>{number = 1, name = main}
mainAsync2: <NSThread: 0x6000031920c0>{number = 1, name = main}

可以看出主队列不管同步还是异步执行任务都是在主线程中的。

串行队列
DispatchQueue(label: "queue").async {
    print("queue: \(Thread.current)")
    let serialQueue = DispatchQueue(label: "serial")
    serialQueue.sync {
        print("serialSync1: \(Thread.current)")
    }
    serialQueue.sync {
        print("serialSync2: \(Thread.current)")
    }
    serialQueue.async {
        print("serialAsync1: \(Thread.current)")
    }
    serialQueue.async {
        print("serialAsync2: \(Thread.current)")
    }
}
打印1:
queue: <NSThread: 0x6000038128c0>{number = 3, name = (null)}
serialSync1: <NSThread: 0x6000038128c0>{number = 3, name = (null)}
serialSync2: <NSThread: 0x6000038128c0>{number = 3, name = (null)}
serialAsync1: <NSThread: 0x6000038128c0>{number = 3, name = (null)}
serialAsync2: <NSThread: 0x6000038128c0>{number = 3, name = (null)}
打印2:
queue: <NSThread: 0x600001758fc0>{number = 3, name = (null)}
serialSync1: <NSThread: 0x600001758fc0>{number = 3, name = (null)}
serialSync2: <NSThread: 0x600001758fc0>{number = 3, name = (null)}
serialAsync1: <NSThread: 0x600001762700>{number = 4, name = (null)}
serialAsync2: <NSThread: 0x600001762700>{number = 4, name = (null)}

可以看出串行队列同步执行任务的时候是在当前线程,而异步执行任务的时候可能在当前线程,也可能是新开一条线程来处理。

DispatchQueue(label: "queue").async {
    print("queue: \(Thread.current)")
    let serialQueue = DispatchQueue(label: "serial")
    serialQueue.async {
        Thread.sleep(forTimeInterval: 2)
        print("serialAsync2: \(Thread.current)")
    }
    print("xx")
    serialQueue.sync {
        print("serialSync1: \(Thread.current)")
    }
    print("yy")
}
打印:
queue: <NSThread: 0x6000000714c0>{number = 3, name = (null)}
xx
2秒后:
serialAsync2: <NSThread: 0x600000051e80>{number = 4, name = (null)}
serialSync1: <NSThread: 0x6000000714c0>{number = 3, name = (null)}
yy

可以看出在串行队列中异步任务虽然立即返回了,但当后续还有其他任务时,依然要等待异步任务执行完成后才能执行,造成线程阻塞。

并行队列
DispatchQueue(label: "queue").async {
    print("queue: \(Thread.current)")
    let concurrentQueue = DispatchQueue(label: "concurrent", qos: .default, attributes: .concurrent)
    concurrentQueue.sync {
        print("concurrentSync1: \(Thread.current)")
    }
    concurrentQueue.sync {
        print("concurrentSync2: \(Thread.current)")
    }
    concurrentQueue.async {
        print("concurrentAsync1: \(Thread.current)")
    }
    concurrentQueue.async {
        print("concurrentAsync2: \(Thread.current)")
    }
}

打印:
queue: <NSThread: 0x600001b76540>{number = 3, name = (null)}
concurrentSync1: <NSThread: 0x600001b76540>{number = 3, name = (null)}
concurrentSync2: <NSThread: 0x600001b76540>{number = 3, name = (null)}
concurrentAsync1: <NSThread: 0x600001b76540>{number = 3, name = (null)}
concurrentAsync2: <NSThread: 0x600001b76540>{number = 3, name = (null)}

可以看出并行队列同步执行任务时候是在当前线程,异步任务也可能是在当前线程。

DispatchQueue(label: "queue").async {
    print("queue: \(Thread.current)")
    let concurrentQueue = DispatchQueue(label: "concurrent", qos: .default, attributes: .concurrent)
    concurrentQueue.sync {
        Thread.sleep(forTimeInterval: 2)
        print("concurrentSync1: \(Thread.current)")
    }
    print("xx")
    concurrentQueue.async {
        Thread.sleep(forTimeInterval: 2)
        print("concurrentAsync1: \(Thread.current)")
    }
    print("yy")
    concurrentQueue.async {
        Thread.sleep(forTimeInterval: 2)
        print("concurrentAsync2: \(Thread.current)")
    }
    print("zz")
    concurrentQueue.sync {
        print("concurrentSync2: \(Thread.current)")
    }
}

打印:
queue: <NSThread: 0x60000311f6c0>{number = 3, name = (null)}
2秒后:
concurrentSync1: <NSThread: 0x60000311f6c0>{number = 3, name = (null)}
xx
yy
zz
concurrentSync2: <NSThread: 0x60000311f6c0>{number = 3, name = (null)}
4秒后:
concurrentAsync2: <NSThread: 0x60000310ae40>{number = 4, name = (null)}
concurrentAsync1: <NSThread: 0x60000311f6c0>{number = 3, name = (null)}

可以看出并行队列同步执行任务的时候也会阻塞当前线程,异步执行任务则会在当前线程或者新开线程执行而且不会阻塞线程。

总结
  • 主队列的任务都在主线程执行,在主线程上调用主队列同步执行任务会造成死锁
  • 同步执行任务都在当前线程
  • 异步执行任务也可能会在当前线程,也可能新开线程(串行队列会新开一条,并行队列会新开多条)
  • 串行队列执行任务都是一个接一个执行的,就算是异步任务,也只是提前返回,后续任务也要等该异步任务执行完成后才能继续
  • 并行队列异步执行任务的时候是开启多个线程并发执行多个任务,不会造成线程阻塞
主队列(main) 串行队列(serial) 并行队列(concurrent)
同步(sync) 主线程 当前线程 当前线程
异步(async) 主线程 当前线程/新线程(一个) 当前线程/新线程(多个)

关于同步执行任务都在当前线程,官方文档 描述如下:

As a performance optimization, this function executes blocks on the current thread whenever possible, with one obvious exception. Specifically, blocks submitted to the main dispatch queue always run on the main thread.

其他多线程编程技术

GCD 的几种操作

Group

打包几个异步任务,等待它们都执行完之后发出通知。

func group() {
    let group = DispatchGroup()
    let queue = DispatchQueue(label: "groupQueue", qos: .default, attributes: .concurrent)

    queue.async(group: group) {
        Thread.sleep(forTimeInterval: 2)
        print("1111")
    }

    queue.async(group: group) {
        Thread.sleep(forTimeInterval: 5)
        print("2222")
    }

    group.notify(queue: queue) {
        print("over")
    }

    print("xxxx")
}

打印:
xxxx
1111
2222
over
barrier

栅栏任务的主要特性是可以对队列中的任务进行阻隔,执行栅栏任务时,它会先等待队列中已有的任务全部执行完成,然后它再执行,在它之后加入的任务也必须等栅栏任务执行完后才能执行。

func barrier() {
    let concurrentQueue = DispatchQueue(label: "concurrent", qos: .default, attributes: .concurrent)
    concurrentQueue.async {
        Thread.sleep(forTimeInterval: 2)
        print("concurrentAsync1: \(Thread.current)")
    }
    concurrentQueue.async {
        Thread.sleep(forTimeInterval: 2)
        print("concurrentAsync2: \(Thread.current)")
    }

    concurrentQueue.async(flags: .barrier) {
        Thread.sleep(forTimeInterval: 4)
        print("栅栏")
        print("barTask: \(Thread.current)")
    }

    concurrentQueue.async {
        Thread.sleep(forTimeInterval: 2)
        print("concurrentAsync3: \(Thread.current)")
    }
    concurrentQueue.async {
        Thread.sleep(forTimeInterval: 2)
        print("concurrentAsync4: \(Thread.current)")
    }
}

//另一种写法
func barrier() {
    let barTask = DispatchWorkItem(flags: .barrier) {
        Thread.sleep(forTimeInterval: 4)
        print("栅栏")
        print("barTask: \(Thread.current)")
    }

    let concurrentQueue = DispatchQueue(label: "concurrent", qos: .default, attributes: .concurrent)
    concurrentQueue.async {
        Thread.sleep(forTimeInterval: 2)
        print("concurrentAsync1: \(Thread.current)")
    }
    concurrentQueue.async {
        Thread.sleep(forTimeInterval: 2)
        print("concurrentAsync2: \(Thread.current)")
    }

    concurrentQueue.async(execute: barTask)
    //        barTask.wait()

    concurrentQueue.async {
        Thread.sleep(forTimeInterval: 2)
        print("concurrentAsync3: \(Thread.current)")
    }
    concurrentQueue.async {
        Thread.sleep(forTimeInterval: 2)
        print("concurrentAsync4: \(Thread.current)")
    }
}

打印:
concurrentAsync1: <NSThread: 0x60000180aa40>{number = 3, name = (null)}
concurrentAsync2: <NSThread: 0x60000182a540>{number = 4, name = (null)}
栅栏
barTask: <NSThread: 0x60000182a540>{number = 4, name = (null)}
concurrentAsync3: <NSThread: 0x60000182a540>{number = 4, name = (null)}
concurrentAsync4: <NSThread: 0x600001836d00>{number = 5, name = (null)}
semaphore

DispatchSemaphore,通常称作信号量,顾名思义,它可以通过计数来标识一个信号,这个信号怎么用呢,取决于任务的性质。通常用于对同一个资源访问的任务数进行限制。例如,控制同一时间写文件的任务数量、控制端口访问数量、控制下载任务数量等。

func semaphore() {
    let queue = DispatchQueue.global()
    let sem = DispatchSemaphore(value: 2)

    let _ = sem.wait(timeout: DispatchTime.now() + 5)
    queue.async {
        Thread.sleep(forTimeInterval: 2)
        print("task 1 over")
        sem.signal()
    }

    sem.wait()
    queue.async {
        Thread.sleep(forTimeInterval: 2)
        print("task 2 over")
        sem.signal()
    }

    sem.wait()
    queue.async {
        Thread.sleep(forTimeInterval: 2)
        print("task 3 over")
        sem.signal()
    }
}

打印:
task 1 over
task 2 over
2秒后:
task 3 over
concurrentPerform

并行队列利用多个线程执行任务,可以提高程序执行的效率。而迭代任务可以更高效地利用多核性能,它可以利用 CPU 当前所有可用线程进行计算(任务小也可能只用一个线程)。如果一个任务可以分解为多个相似但独立的子任务,那么迭代任务是提高性能最适合的选择。
使用 concurrentPerform 方法执行迭代任务,迭代任务的后续任务需要等待它执行完成才会继续。

func concurrentPerform() {
    var reslut = [Int]()
    DispatchQueue.global().async {
        DispatchQueue.concurrentPerform(iterations: 100, execute: { (index) in
            if index % 13 == 0 {
                print("current: \(Thread.current)")
                DispatchQueue.main.async {
                    reslut.append(index)
                }
            }
        })

        DispatchQueue.main.async {
            print(reslut)
        }
    }
}

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

推荐阅读更多精彩内容