GCD是苹果开发的多线程编程的解决方案,经过简单的API就能够实现建立新线程去执行咱们须要执行的任务,不须要咱们手动地建立和管理线程,只须要建立队列和相应的函数配合使用就行。它的API包含在libdispatch库中。程序员
GCD全称Grand Central Dispatch,是Apple提供的一套底层API,提供了一种新的方法来进行并发程序编写。GCD有点像NSOperationQueue,但它比NSOpertionQueue更底层更高效,而且它不属于Cocoa框架。GCD的API很大程度上基于block,固然,GCD也能够脱离block来使用,好比使用传统C机制提供函数指针和上下文指针。实践证实,当配合block使用时,GCD很是简单易用且能发挥其最大能力。编程
任务(Task): 就是执行操做的意思,换句话说就是你在线程中执行的那段代码。在 GCD 中是放在 block 中的。执行任务有两种方式:**同步执行(sync)**和 异步执行(async)。二者的主要区别是:是否等待队列的任务执行结束,以及是否具有开启新线程的能力。api
队列(Queue) 这里的队列指执行任务的等待队列,即用来存听任务的队列。队列是一种特殊的线性表,采用 FIFO(先进先出)的原则,即新任务老是被插入到队列的末尾,而读取任务的时候老是从队列的头部开始读取。每读取一个任务,则从队列中释放一个任务。安全
GCD中队列的种类bash
GCD建立队列多线程
主队列(串行队列)闭包
let mainQueue = DispatchQueue.main
复制代码
全局并行队列并发
let globalQueue = DispatchQueue.global(qos: .default)
复制代码
建立串行队列框架
let serialQueue = DispatchQueue(label: "vip.mybadge")
复制代码
建立并行队列异步
let concurQueue = DispatchQueue(label: "vip.mybadge", attributes: .concurrent)
复制代码
执行任务
func task(i: Int) {
print("\(i) thread = \(Thread.current)")
}
for i in 0..<100 {
serialQueue.async {
task(i: i)
}
}
复制代码
输出结果
...
11 thread = <NSThread: 0x60000373fa00>{number = 5, name = (null)}
12 thread = <NSThread: 0x60000373fa00>{number = 5, name = (null)}
13 thread = <NSThread: 0x60000373fa00>{number = 5, name = (null)}
14 thread = <NSThread: 0x60000373fa00>{number = 5, name = (null)}
15 thread = <NSThread: 0x60000373fa00>{number = 5, name = (null)}
16 thread = <NSThread: 0x60000373fa00>{number = 5, name = (null)}
17 thread = <NSThread: 0x60000373fa00>{number = 5, name = (null)}
18 thread = <NSThread: 0x60000373fa00>{number = 5, name = (null)}
19 thread = <NSThread: 0x60000373fa00>{number = 5, name = (null)}
...
复制代码
能够发如今串行队列中, 等待队列的任务执行结束,不具有开启新线程的能力
func task(i: Int) {
print("\(i) thread = \(Thread.current)")
}
for i in 0..<100 {
globalQueue.async {
task(i: i)
}
}
复制代码
输出结果
...
75 thread = <NSThread: 0x600002aef1c0>{number = 5, name = (null)}
76 thread = <NSThread: 0x600002aef1c0>{number = 5, name = (null)}
77 thread = <NSThread: 0x600002aef480>{number = 6, name = (null)}
78 thread = <NSThread: 0x600002aef1c0>{number = 5, name = (null)}
79 thread = <NSThread: 0x600002aef480>{number = 6, name = (null)}
80 thread = <NSThread: 0x600002af1280>{number = 8, name = (null)}
81 thread = <NSThread: 0x600002af16c0>{number = 9, name = (null)}
82 thread = <NSThread: 0x600002af1400>{number = 10, name = (null)}
83 thread = <NSThread: 0x600002af1340>{number = 11, name = (null)}
84 thread = <NSThread: 0x600002af1380>{number = 12, name = (null)}
...
复制代码
能够发如今并行队列中, 不等待队列的任务执行结束,具有开启新线程的能力
若是想等到全部的队列的任务执行完毕再进行后序操做时,可使用DispatchGroup来完成。
let group = DispatchGroup()
for i in 0..<5 {
print("任务\(i+1)下载中...")
DispatchQueue.global().async(group: group) {
Thread.sleep(forTimeInterval: 1)
print("任务\(i+1)下载完成")
}
}
group.notify(queue: DispatchQueue.main) {
print("任务都下载完成...去更新UI")
}
复制代码
执行结果
任务1下载中...
任务2下载中...
任务3下载中...
任务4下载中...
任务5下载中...
任务1下载完成
任务3下载完成
任务5下载完成
任务4下载完成
任务2下载完成
任务都下载完成...去更新UI
复制代码
Swift3新增的类,能够经过此类设置队列执行的任务。至关于把原来GCD中闭包的代码封装到了这里, 看一个例子:
let workItem = DispatchWorkItem {
for i in 0..<10 {
print(i)
}
}
DispatchQueue.global().async(execute: workItem)
复制代码
看看他的初始化方法
init(qos: DispatchQoS = default, flags: DispatchWorkItemFlags = default,
block: @escaping () -> Void)
复制代码
从初始化方法能够看出,DispatchWorkItem也能够设置优先级,另外还有个参数DispatchWorkItemFlags,来看看DispatchWorkItemFlags的内部组成:
public struct DispatchWorkItemFlags : OptionSet, RawRepresentable {
// 至关于以前的栅栏函数
public static let barrier: DispatchWorkItemFlags
public static let detached: DispatchWorkItemFlags
public static let assignCurrentContext: DispatchWorkItemFlags
// 没有优先级
public static let noQoS: DispatchWorkItemFlags
// 继承Queue的优先级
public static let inheritQoS: DispatchWorkItemFlags
// 覆盖Queue的优先级
public static let enforceQoS: DispatchWorkItemFlags
}
复制代码
能够理解为隔离,仍是以文件读写为例,在读取文件时,能够异步访问,可是若是忽然出现了异步写入操做,咱们想要达到的效果是在进行写入操做的时候,使读取操做暂停,直到写入操做结束,再继续进行读取操做,以保证读取操做获取的是文件的最新内容。
先看看不使用barrier的例子
let concurQueue = DispatchQueue(label: "vip.mybadge", attributes: .concurrent)
struct File {
var content = ""
}
var file = File()
file.content = "This is a file"
let writeFileWorkItem = DispatchWorkItem {
file.content = "This file has been modified."
Thread.sleep(forTimeInterval: 1)
print("write file")
}
let readFileWorkItem = DispatchWorkItem {
Thread.sleep(forTimeInterval: 1)
print("file.content=\(file.content)")
}
for _ in 0..<3 {
concurQueue.async(execute: readFileWorkItem)
}
concurQueue.async(execute: writeFileWorkItem)
for _ in 0..<3 {
concurQueue.async(execute: readFileWorkItem)
}
复制代码
输出结果
file.content=This file has been modified.
write file
file.content=This file has been modified.
file.content=This file has been modified.
file.content=This file has been modified.
file.content=This file has been modified.
file.content=This file has been modified.
复制代码
咱们指望的结果是,在写文件以前,打印 "This is a file", 写文件以后打印的是"This file has been modified.", 上面结果显然不是咱们想要的。 看一下使用barrier的效果
let concurQueue = DispatchQueue(label: "vip.mybadge", attributes: .concurrent)
struct File {
var content = ""
}
var file = File()
file.content = "This is a file"
let writeFileWorkItem = DispatchWorkItem(flags: .barrier) {
file.content = "This file has been modified."
Thread.sleep(forTimeInterval: 1)
print("white file")
}
let readFileWorkItem = DispatchWorkItem {
Thread.sleep(forTimeInterval: 1)
print("file.content=\(file.content)")
}
for _ in 0..<3 {
concurQueue.async(execute: readFileWorkItem)
}
concurQueue.async(execute: writeFileWorkItem)
for _ in 0..<3 {
concurQueue.async(execute: readFileWorkItem)
}
复制代码
输出结果
file.content=This is a file.
file.content=This is a file.
file.content=This is a file.
write file
file.content=This file has been modified.
file.content=This file has been modified.
file.content=This file has been modified.
复制代码
结果符合预期的想法,barrier主要用于读写隔离,以保证写入的时候,不被读取。
DispatchSemaphore中的信号量,能够解决资源抢占的问题,支持信号的通知和等待.每当发送一个信号通知,则信号量+1;每当发送一个等待信号时信号量-1,若是信号量为0则信号会处于等待状态.直到信号量大于0开始执行.因此咱们通常将DispatchSemaphore的value设置为1.
线程同步: 可理解为线程A和线程B一块配合, A执行到必定程度时要依靠B的某个结果, 因而停下来, 示意B运行; B依言执行, 再将结果给A; A再继续操做.
/// 信号量的线程同步.
func semaphoreSync() {
var number = 0
let semaphoreSignal = DispatchSemaphore(value: 0)
let globalQueue = DispatchQueue.global()
let workItem = DispatchWorkItem {
Thread.sleep(forTimeInterval: 1)
print("change number, thread=\(Thread.current)")
number = 100
semaphoreSignal.signal()
}
print("semaphore begin")
print("number = \(number), thread=\(Thread.current)")
globalQueue.async(execute: workItem)
semaphoreSignal.wait()
print("number = \(number)")
print("semaphore end")
}
semaphoreSync()
复制代码
输出
semaphore begin
number = 0, thread=<NSThread: 0x6000007ca900>{number = 1, name = main}
change number, thread=<NSThread: 0x6000007e8180>{number = 5, name = (null)}
number = 100
semaphore end
复制代码
semaphore end 是在执行完 number = 100; 以后才打印的。并且输出结果 number 为 100。
这样就实现了线程同步,将异步执行任务转换为同步执行任务。
下面,咱们模拟火车票售卖的方式,实现 NSThread 线程安全和解决线程同步问题。
场景:总共有10张火车票,有两个售卖火车票的窗口,一个是北京火车票售卖窗口,另外一个是上海火车票售卖窗口。两个窗口同时售卖火车票,卖完为止。
先来看看不考虑线程安全的代码
class SaleTicketNotSafe {
private var ticketSurplusCount = 0
private let semaphoreSignal = DispatchSemaphore(value: 1)
private let serialQueue = DispatchQueue(label: "vip.mybadge.dispatch")
private let serialQueue2 = DispatchQueue(label: "vip.mybadge.dispatch")
init(ticketSurplusCount: Int) {
self.ticketSurplusCount = ticketSurplusCount
}
func startSaleNotSave() {
print("current thread=\(Thread.current)")
serialQueue.async { [weak self] in
self?.saleTicketNotSafe()
}
serialQueue2.async { [weak self] in
self?.saleTicketNotSafe()
}
}
private func saleTicketNotSafe() {
while true {
if ticketSurplusCount > 0 {
ticketSurplusCount -= 1
print("剩余票数\(ticketSurplusCount), 窗口:\(Thread.current)")
Thread.sleep(forTimeInterval: 1)
} else {
print("全部票都售完了")
break
}
}
}
}
let saleTicket = SaleTicketNotSafe(ticketSurplusCount: 10)
saleTicket.startSaleNotSave()
复制代码
输出结果
开始售票 thread=<NSThread: 0x600003802900>{number = 1, name = main}
剩余票数9, 窗口:<NSThread: 0x600003824c00>{number = 6, name = (null)}
剩余票数8, 窗口:<NSThread: 0x6000038157c0>{number = 4, name = (null)}
剩余票数6, 窗口:<NSThread: 0x6000038157c0>{number = 4, name = (null)}
剩余票数7, 窗口:<NSThread: 0x600003824c00>{number = 6, name = (null)}
剩余票数4, 窗口:<NSThread: 0x6000038157c0>{number = 4, name = (null)}
剩余票数4, 窗口:<NSThread: 0x600003824c00>{number = 6, name = (null)}
剩余票数3, 窗口:<NSThread: 0x6000038157c0>{number = 4, name = (null)}
剩余票数2, 窗口:<NSThread: 0x600003824c00>{number = 6, name = (null)}
剩余票数1, 窗口:<NSThread: 0x6000038157c0>{number = 4, name = (null)}
剩余票数0, 窗口:<NSThread: 0x600003824c00>{number = 6, name = (null)}
全部票都售完了
全部票都售完了
复制代码
线程安全的代码
class SaleTicketSafe {
private var ticketSurplusCount = 0
private let semaphoreSignal = DispatchSemaphore(value: 1)
private let serialQueue = DispatchQueue(label: "vip.mybadge.dispatch")
private let serialQueue2 = DispatchQueue(label: "vip.mybadge.dispatch")
init(ticketSurplusCount: Int) {
self.ticketSurplusCount = ticketSurplusCount
}
func startSaleSave() {
print("开始售票 thread=\(Thread.current)")
serialQueue.async { [weak self] in
self?.saleTicketSafe()
}
serialQueue2.async { [weak self] in
self?.saleTicketSafe()
}
}
private func saleTicketSafe() {
while true {
semaphoreSignal.wait()
if ticketSurplusCount > 0 {
ticketSurplusCount -= 1
print("剩余票数\(ticketSurplusCount), 窗口:\(Thread.current)")
Thread.sleep(forTimeInterval: 1)
} else {
semaphoreSignal.signal()
print("全部票都售完了")
break
}
semaphoreSignal.signal()
}
}
}
let saleTicket = SaleTicketSafe(ticketSurplusCount: 10)
saleTicket.startSaleSave()
复制代码
输出结果
开始售票 thread=<NSThread: 0x600001ac6900>{number = 1, name = main}
剩余票数9, 窗口:<NSThread: 0x600001ad4b80>{number = 4, name = (null)}
剩余票数8, 窗口:<NSThread: 0x600001ad8640>{number = 6, name = (null)}
剩余票数7, 窗口:<NSThread: 0x600001ad4b80>{number = 4, name = (null)}
剩余票数6, 窗口:<NSThread: 0x600001ad8640>{number = 6, name = (null)}
剩余票数5, 窗口:<NSThread: 0x600001ad4b80>{number = 4, name = (null)}
剩余票数4, 窗口:<NSThread: 0x600001ad8640>{number = 6, name = (null)}
剩余票数3, 窗口:<NSThread: 0x600001ad4b80>{number = 4, name = (null)}
剩余票数2, 窗口:<NSThread: 0x600001ad8640>{number = 6, name = (null)}
剩余票数1, 窗口:<NSThread: 0x600001ad4b80>{number = 4, name = (null)}
剩余票数0, 窗口:<NSThread: 0x600001ad8640>{number = 6, name = (null)}
全部票都售完了
全部票都售完了
复制代码
能够看出,在考虑了线程安全的状况下,使用 DispatchSemaphore 机制以后,获得的票数是正确的,没有出现混乱的状况。咱们也就解决了线程安全与线程同步的问题。
以上代码能够直接在Playground中运行
为总结学习而写,如有错误,欢迎指正。