身就是顺序执行,屏障不会起到任何帮助作用。
全局并发队列:慎用。其他系统可能也在使用队列,你不应该出于自身目的而独占队列。
自定义并发队列:最佳选择。用于原子操作或是临界区代码。任何需要线程安全的设置和初始化都可以使用屏障。
因为以上唯一合适的选择就是自定义并发队列,你需要生成一个这样的队列来处理屏障函数以隔离读写操作。并发队列允许多个线程同时的读操作。
打开PhotoManager.swift并在photos属性下面添加如下私有属性到类中:
1
2
|
private let concurrentPhotoQueue = dispatch_queue_create(
"com.raywenderlich.GooglyPuff.photoQueue"
, DISPATCH_QUEUE_CONCURRENT)
|
使用dispatch_queue_create初始化一个并发队列concurrentPhotoQueue。第一个参数遵循反向DNS命名习惯;保证描述性以利于调试。第二个参数指出你的队列是顺序的还是并发的。
注意:当在网上搜索例子时,你经常看到人们传0或NULL作为dispatch_queue_create的第二个参数。这是一种过时的方法来生成顺序调度队列;最好用参数显示声明。
找到addPhoto并用如下实现替换之:
1
2
3
4
5
6
7
8
|
func addPhoto(photo: Photo) {
dispatch_barrier_async(concurrentPhotoQueue) {
// 1
self._photos.append(photo)
// 2
dispatch_async(GlobalMainQueue) {
// 3
self.postContentAddedNotification()
}
}
}
|
来 看这段代码如何工作的: 1. 将写操作加入自定义的队列中。当临界区被执行时,这是队列中唯一一个在执行的任务。 2. 将对象加入数组。因为是屏障闭包,这个闭包不会和concurrentPhotoQueue中的其他任务同时执行。 3. 最终发送一个添加了图片的通知。这个通知应该在主线程中发送因为这涉及到UI,所以这里分派另一个异步任务到主队列中。
这个任务解决了写问题,但是你还需要实现photos的读方法。
为确保和写操作保持线程安全,你需要在concurrentPhotoQueue中执行读操作。但是你需要从函数返回读数据,所以不能异步地提交读操作到队列里,因为异步任务不能保证在函数返回前执行。
因此,dispatch_sync是个极好的候选。
dispatch_sync同步提交任务并等到任务完成后才返回。使用dispatch_sync和调度屏障一起来跟踪任务;或是在需要等待返回数据时使用dispatch_sync。
仍 需小心。设想你调用dispatch_sync到当前队列中。这会造成死锁。因为调用在等待闭包完成,但是闭包无法完成(甚至根本没开始!),直到当前在 执行的任务结束,但当前任务没法结束(因为阻塞的闭包还没完成)!这就要求你必须清醒的认识到你从哪个队列调用了闭包,以及你将任务提交到哪个队列。
概 述一下何时何地使用dispatch_sync: – 自定义顺序队列:非常小心;如果你在运行一个队列时调用dispatch_sync调度任务到同一个队列,你显然会制造死锁。 – 主队列(顺序):非常小心,原理同上。 – 并发队列:好选择。用在和调度屏障同步或是等待任务完成以继续后续处理。 还是在PhotoManager.swift中,替换photos如下:
1
2
3
4
5
6
7
|
var
photos: [Photo] {
var
photosCopy: [Photo]!
dispatch_sync(concurrentPhotoQueue) {
// 1
photosCopy = self._photos
// 2
}
return
photosCopy
}
|
分别来看每个号码注释: 1. 同步调度到concurrentPhotoQueue队列执行读操作。 2. 保存图片数组的拷贝到photoCopy并返回它。
恭喜 —— 你的PhotoManager单例已经是线程安全的了。不论你读或是写图片数组,你都有信心保证操作会安全的执行。
回顾
还是不能100%的确定GCD的本质?你可以自己创建使用GCD函数的简单例子,通过断点和NSLog来确保你明白发生了什么。
我这里有两张动态GIF图片来帮助你理解dispatch_async和dispatch_sync。每张GIF上面都有代码辅助你理解;注意代码中的断点和相应的队列状态。
重访dispatch_sync
1
2
3
4
5
6
7
8
9
10
11
12
|
override func viewDidLoad() {
super
.viewDidLoad()
dispatch_sync(dispatch_get_global_queue(
Int(QOS_CLASS_USER_INTERACTIVE.value), 0)) {
NSLog(
"First Log"
)
}
NSLog(
"Second Log"
)
}
|
下面对图片中的几个状态做说明:
1. 主队列按部就班的执行任务 —— 紧接着的任务是实例化包含viewDidL