iOS 并发,锁,线程同步【二】Operation
在之前的一篇文章中【iOS 并发,锁,线程同步【一】GCD】,我们讨论了一下 GCD 的并发,锁和线程同步的问题,今天,我们来讨论一下 Operation 的并发与线程同步。
在 Operation 中,我们一般是将所有的 Operation 添加到 OperationQueue 中进行执行,这里需要注意一点,**Operation 添加到队列当中,默认就是执行的并发操作。**我们可以设置队列的最大并发数 maxConcurrentOperationCount。如果我们在 OperationQueue 中想要执行串行任务的话,很简单,将 maxConcurrentOperationCount 设置成为1即可。 maxConcurrentOperationCount 的默认值为-1,那么默认情况下的并发数是多少呢?**这个是由系统内存和 CPU 决定的,可能内存多久开多一点,内存少就开少一点。**最大并发数建议 2~3,如果并发数太多会导致 UI 卡顿。
不添加到队列当中的 Operation,我们可以调用 start() 方法开始一个操作,也可以调用 cancel() 取消等待中的操作,注意:**已经开始执行的操作是没法取消的。**代码示例如下:
1  | let opt = BlockOperation {  | 
如果我们需要进行线程同步该怎么做?GCD 中我们可以用 DispatchGroup,在 Operation 中我们可以用一个 addDependency() 的方法。这个方法意味着,某个任务的执行,依赖着其他任务执行完成后才回去执行。代码我们可以这么写:
1  | let queue = OperationQueue()  | 
这里的 c 操作,需要等到 a, b 完成之后才会执行。运行结果如下:
1  | b  | 
OK,接下来我们来一点在并发中进行数据写入的操作,代码作如下修改:
1  | let queue = OperationQueue()  | 
运行结果:
1  | [0, 2, 3, 4, 6, 8, 9]  | 
很明显,运行结果是错的。并发当中对同一个数据源进行写的操作时,一定要注意加锁。具体可以看我的上一遍文章:iOS 并发,锁,线程同步【一】GCD,这里我就不在啰嗦。
接下来,我们做一点代码优化,如果我想要实现 n 个任务,每个任务都是向数组中添加数字,每个任务的循环范围按照 05,510,10~15 这样的规律,最后我们输出 ary 中的值。
很明显,向上面的写法太过笨拙。那么我们进行一个函数的抽象,我们先来写一个产生 task 的函数。
首先我们先来定义一个 task:
1  | typealias task = () -> ()  | 
接下来,我们需要将产生的 task 添加到数组中,这里需要充分利用函数式编程的优点,方法看起来是这样:
1  | func makeTask(taskCount: Int, opt: @escaping (_ currentIdx: Int) -> task) -> [task] {  | 
第一个参数 taskCount 是最大任务数,第二个参数 opt 是执行的任务,是一个闭包,因为任务会存到一个数组中,供后面的方法使用,所以这个闭包是可逃逸的。我们在执行闭包的时候,需要传入一个参数,这个参数表示了当前生成的是第几个 task,返回值也是个闭包,也就是我们最终要执行的 task。OK,经过函数化,我们就可以产生任意数量、任意操作的 task 了。
接下来我们来处理一下并发的方法,它看起来是这样的:
1  | func concurrent(tasks: [task], complationHandle: @escaping () -> ()) {  | 
原理也很简单,我们在一个 forEach 当中设置好 Operation 的 task 与依赖关系。这里用了信号量锁,来保证数据的正确性。最后我们在 complationHandle 这个闭包中处理同步后的数据。
我们来使用一下,感受一下函数式的灵活、强大之处:
1  | var ary: [Int] = []  | 
输出结果如下:
1  | task idx: 0  | 
调用还是有点麻烦?没关系我们可以将两个方法合成一个,我们把 makeTask 与 concurrent 方法设置成为私有(private),接下来写一个开放接口方法:
1  | public func tasksToConcurrent(taskCount: Int, opt: @escaping (_ currentIdx: Int) -> task, complationHandle: @escaping () -> ()) {  | 
我们就可以这样来调用:
1  | tasksToConcurrent(taskCount: 20, opt: { idx in  | 
感觉比刚开始的写法简洁了不少!!!