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 |
感觉比刚开始的写法简洁了不少!!!