GCD精讲(Swift 3&4)

本文详细讲解了Swift 3和4中的GCD(Grand Central Dispatch),包括DispatchQueue、队列分类、async/sync、QoS、DispatchWorkItem、DispatchSource、DispatchGroup、Semaphore、Barrier等功能,以及同步和死锁的处理。通过实例展示了如何使用GCD进行多线程编程,强调了线程安全和性能优化。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

前言

我们首先来看一张图:

我觉得这张动图很好的诠释了《把一个线程用到死的》核心价值观。

很多程序都有一个主线程。对于iOS/MacOS开发来说,这个线程就是UI线程,在这个线程上会做一些用户交互/渲染相关的事情。把过多的任务放在主线程,会导致主线程卡顿,进而用户看到的就是App响应慢,列表滚动的时候掉帧。

把任务分散到多个线程执行有很多种技术,在iOS/MacOS App开发中,最简单直观的就是GCD(又叫Dispatch)了。Swift 3把冗长的GCD API进行了精简和优化,所以很多时候,我们都可以使用GCD来进行多线程开发。

本文使用到的playground可以在我的github上下载到。如果你不熟悉Playground的基本操作,欢迎阅读的上一篇文章

本文很长,讲解范围从基础的概念,到async/sync,QoS,Sources,Group,Semaphore,Barrier,再到最后的同步和死锁,本文的Playground可以在这里下载。


Dispatch是啥

Dispatch comprises language features, runtime libraries, and system enhancements that provide systemic, comprehensive improvements to the support for concurrent code execution on multicore hardware in macOS, iOS, watchOS, and tvOS.

大致的意思是

Dispatch结合语言特性,运行时,和系统的特点,提供了系统的,全面的高层次API来提升多核多线程编程的能力。

Dispatch会自动的根据CPU的使用情况,创建线程来执行任务,并且自动的运行到多核上,提高程序的运行效率。对于开发者来说,在GCD层面是没有线程的概念的,只有队列(queue)。任务都是以block的方式提交到对列上,然后GCD会自动的创建线程池去执行这些任务。在

对于Swift 3来说,Dispatch是一个module.你可以通过import进行导入

import Dispatch

这里,我们新建一个playgorund来运行本文的释例代码,并且命名为Dispatch.playground

关于Swift3.0 中GCD 的改变,参见


DispatchQueue

DispatchQueue是一个类似线程的概念,这里称作对列队列是一个FIFO数据结构,意味着先提交到队列的任务会先开始执行)。DispatchQueue背后是一个由系统管理的线程池。

最简单的,可以按照以下方式初始化一个队列

//这里的名字能够方便开发者进行Debug
let queue = DispatchQueue(label: "com.Leo.demoQueue")

这样初始化的队列是一个默认配置的队列,也可以显式的指明对列的其他属性

let label = "com.leo.demoQueue"
let qos =  DispatchQoS.default
let attributes = DispatchQueue.Attributes.concurrent
let autoreleaseFrequency = DispatchQueue.AutoreleaseFrequency.never
let queue = DispatchQueue(label: label, qos: qos, attributes: attributes, autoreleaseFrequency: autoreleaseFrequency, target: nil)

这里,我们来一个参数分析他们的作用

  • label 队列的标识符,方便调试
  • qos 队列的quality of service。用来指明队列的“重要性”,后文会详细讲到。
  • attributes 队列的属性。类型是DispatchQueue.Attributes,是一个结构体,遵循了协议OptionSet。意味着你可以这样传入第一个参数[.option1,.option2]
  • autoreleaseFrequency。顾名思义,自动释放频率。有些队列是会在执行完任务后自动释放的,有些比如Timer等是不会自动释放的,是需要手动释放。

队列分类

创建者来讲,队列可以分为两种,其中系统创建的队列又分为两种

  • 系统创建的队列
    • 主队列(对应主线程)
    • 全局队列
  • 用户创建的队列

主队列/全局队列可以这样获取

let mainQueue = DispatchQueue.main
let globalQueue = DispatchQueue.global()
let globalQueueWithQos = DispatchQueue.global(qos: .userInitiated)

从任务的执行情况来讲,可以分为

  • 串行(serial)
  • 并行(concurrent)

这里我们用一张图,来讲解下什么是串行队列和并行队列。

在Swfit 3.0中,创建一个串行/并行队列

let serialQueue = DispatchQueue(label: "com.leo.serialQueue")
let concurrentQueue = DispatchQueue(label: "com.leo.concurrentQueue",attributes:.concurrent)

async

  • async 提交一段任务到队列,并且立刻返回

举个例子:

我们新建一个方法来模拟一段很长时间的任务,比如读一张很大的图

public func readDataTask(label:String){
    NSLog("Start sync task%@",label)
    sleep(2)
    NSLog("End sync task%@",label)
}

Tips:如果代码运行在Playground里,记得在最上面加上这两行。

import PlaygroundSupport
PlaygroundPage.current.needsIndefiniteExecution = true

然后,我们来看看在serial和concurrent队列上,任务的执行情况。

let serialQueue = DispatchQueue(label: "com.leo.serialQueue")
print("Main queue Start")
serialQueue.async {
    readDataTask(label: "1")
}
serialQueue.async {
    readDataTask(label: "2")
}
print("Main queue End")

会看到Log

Main queue Start
Main queue End
2017-01-04 22:51:40.909 GCD[28376:888938] Start task: 1
2017-01-04 22:51:42.979 GCD[28376:888938] End task: 1
2017-01-04 22:51:42.980 GCD[28376:888938] Start task: 2
2017-01-04 22:51:45.051 GCD[28376:888938] End task: 2

也就是说,任务的执行是

主线程按照顺序提交任务1,任务2到serialQueue,瞬间执行完毕,并没有被阻塞。
serialQueue上先执行任务1,任务1执行完毕后再执行任务2.

再来看看concurrent队列

print("Main queue Start")
let concurrentQueue = DispatchQueue(label: "com.leo.concurrent", attributes: .concurrent)
concurrentQueue.async {
    readDataTask(label: "3")
}
concurrentQueue.async {
    readDataTask(label: "4")
}
print
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值