本文代码:https://github.com/NinoWang/MultithreadingDemo/tree/master
多线程的知识,网上有无数文章来讲述,但真正理解起来还是有点绕的,还是要靠实践才能真正理解,本文以代码为主,少量叙述为辅和大家来捋一下。建议直接下载源码进行理解,不理解的地方在来文章里找答案。话少说,开捋。
说到GCD,通常与相似功能的Operation Queue进行比较理解。
GCD是使用C语言构成的API,而Operation Queue是具体的Objc对象;GCD是使用block的形式管理队列中的任务,而Operation Queue是直接把队列和任务作为具体的对象进行操作。
任务分为同步任务(sync)和异步任务(async)两种。
两者的区别在于,异步任务具备开辟新线程的能力,而同步任务不具备该能力。
队列是执行任务的容器,遵循先进先出(FIFO)的原则,GCD中队列分为串行队列(Serial Dispatch Queue)和并发队列(Concurrent Dispatch Queue)两种。
两者的区别在于,并发队列可以同时执行多个任务(自动开启多个线程),而串行队列只能按顺序逐个执行任务。
另外还有两个特殊的子分类的队列:全局队列(global queue)和主队列(main queue)。
全局队列:并发队列的一种,用来执行较耗时的操作。
主队列:串行队列的一种,只能在主线程中进行,只有主线程空闲的时候才能被执行,用来刷新UI。
可以说GCD中所有场景都是围绕两种任务和两种队列来实现的,不同任务和队列的排列组合:
func conAsync() {
let concurrentQueue = DispatchQueue(label: "Concurrent", attributes: .concurrent)
for i in 0...10 {
concurrentQueue.async {
print("this is NO.\(i), current thread name is \(Thread.current)")
}
}
}
结果为无序
func serAsync() {
let serialQueue = DispatchQueue(label: "Serial")
for i in 0...10 {
serialQueue.async {
print("this is NO.\(i), current thread name is \(Thread.current)")
}
}
}
结果为有序
func mainAsync() {
let mainQueue = DispatchQueue.main
for i in 0...10 {
mainQueue.async {
print("this is NO.\(i), current thread name is \(Thread.current)")
}
}
}
结果为有序
func globalAsync() {
let globalQueue = DispatchQueue.global()
for i in 0...10 {
globalQueue.async {
print("this is NO.\(i), current thread name is \(Thread.current)")
}
}
}
结果为无序
func conSync() {
let concurrentQueue = DispatchQueue(label: "Concurrent", attributes: .concurrent)
for i in 0...10 {
concurrentQueue.sync {
print("this is NO.\(i), current thread name is \(Thread.current)")
}
}
}
结果为有序
func serSync() {
let serialQueue = DispatchQueue(label: "Serial")
for i in 0...10 {
serialQueue.sync {
print("this is NO.\(i), current thread name is \(Thread.current)")
}
}
}
结果为有序
func mainSync() {
let mainQueue = DispatchQueue.main
for i in 0...10 {
mainQueue.sync {
print("this is NO.\(i), current thread name is \(Thread.current)")
}
}
}
死锁造成程序假死
func globalSync() {
let globalQueue = DispatchQueue.global()
for i in 0...10 {
globalQueue.sync {
print("this is NO.\(i), current thread name is \(Thread.current)")
}
}
}
结果为有序
iOS开发中,主线程主要用来处理UI层面的任务,诸如:点击、拖拽、滚动等。而比较耗时的任务则放到子线程中,诸如:数据请求、文件下载上传等。这个时候就需要使用到线程之间的通讯。
let globalQueue = DispatchQueue.global()
globalQueue.async {
if let url = URL.init(string: "https://placebeard.it/200/150") {
do {
let imageData = try Data(contentsOf: url)
let image = UIImage(data: imageData)
DispatchQueue.main.async {
self.imgView.image = image
self.imgView.sizeToFit()
}
} catch {
print(error)
}
}
}
这里的服务优先级决定了对一个任务分配资源的大小,并非绝对的执行顺序。swift3中Qos共有6个级别,优先级从高到低依次为userInteractive、userInitiated、default、utility、background、unspecified。
func QoS() {
// 优先级从高到低 userInteractive、userInitiated、default、utility、background、unspecified
// 指定Qos 这里分别用三种方式指定
// 方式1
let userInteractiveQueue = DispatchQueue(label: "userInteractive", qos: .userInteractive)
let defaultQueue = DispatchQueue(label: "default", qos: .default)
let conQueue = DispatchQueue(label: "con", attributes: .concurrent)
for i in 0...5 {
userInteractiveQueue.async {
print("userInteractive ====> \(i)")
}
defaultQueue.async {
print("defaultQueue ====> \(i)")
}
// 方式2
DispatchQueue.global(qos: .unspecified).async {
print("unspecified ====> \(i)")
}
DispatchQueue.global(qos: .userInitiated).async {
print("userInitiated ====> \(i)")
}
// 方式3
conQueue.async(qos: .utility) {
print("utility ====> \(i)")
}
conQueue.async(qos: .background) {
print("background ====> \(i)")
}
}
}
先看下百度百科的描述信号量的例子:
以一个停车场的运作为例。简单起见,假设停车场只有三个车位,一开始三个车位都是空的。这时如果同时来了五辆车,看门人允许其中三辆直接进入,然后放下车拦,剩下的车则必须在入口等待,此后来的车也都不得不在入口处等待。这时,有一辆车离开停车场,看门人得知后,打开车拦,放入外面的一辆进去,如果又离开两辆,则又可以放入两辆,如此往复。
在这个停车场系统中,车位是公共资源,每辆车好比一个线程,看门人起的就是信号量的作用。
简单来说,信号量起到对多线程调用资源的监管作用。
DispatchSemaphore(value:):用于创建信号量,可以指定初始化信号量计数值,这里我们默认1。
semaphore.wait():会判断信号量,如果为1,则往下执行。如果是0,则等待。
semaphore.signal():代表运行结束,信号量加1,有等待的任务这个时候才会继续执行。
func semaphore() {
let semaphore = DispatchSemaphore(value: 1)
for i in 0...10 {
DispatchQueue.global().async {
semaphore.wait()
print("\(i)")
semaphore.signal()
}
}
}
续篇:Swift3 GCD的基本用法(二) - 队列的循环/挂起/恢复、其他常用方法