iOS的多线程原理、分类与应用
今天查资料才发现,iOS中的线程使用不是无限制的,官方文档给出的资料显示iOS下的主线程堆栈大小是1M,第二个线程开始都是512KB,并且该值不能通过编译器开关或线程API函数来更改。另外只有主线程有直接修改UI的能力。所以也学习并总结下iOS的多线程编程来加深下吧。
关于RunLoop
首先关于RunLoop,iOS中的RunLoop准确的说是线程中的循环。首先循环体的开始需要检测是否有需要处理的事件,如果有则去处理,如果没有则进入睡眠以节省CPU时间。 所以重点便是这个需要处理的事件,在RunLoop中,需要处理的事件分两类,一种是输入源,一种是定时器。定时器好理解,就是那些需要定时执行的操作;输入源分三类:performSelector源,基于端口(Mach port)的源,以及自定义的源。而RunLoop在每一次循环的开始便去检查这些事件源是否有需要处理的数据,有的话则去处理。
系统会自动为应用程序的主线程生成一个与之对应的 run loop 来处理其消息循环。在触摸 UIView 时之所以能够激发 touchesBegan/touchesMoved 等等函数被调用,就是因为应用程序的主线程在 UIApplicationMain 里面有这样一个 run loop 在分发 input 或 timer 事件。
而每一个线程都有其对应的RunLoop,但是默认非主线程的RunLoop是没有运行的,需要为RunLoop添加至少一个事件源,然后去run它。一般情况下我们是没有必要去启用线程的RunLoop的,除非你在一个单独的线程中需要长久的检测某个事件。
多线程编程
iOS中的多线程编程主要分以下三类:1.NSThread;2.NSOperation/NSOperationQueue;3.GCD。后两者其实都是对NSThreads的调用再进行一次封装,以便开发人员更容易使用iOS中的多线程编程。而对于NSOperation/NSOperationQueue和GCD的比较,支持者们意见不太统一,应该适时选择合适的。这里也附上*上的讨论(本文后面也有列出大概原因)情况。
NSThread
- 优点:NSThread比其他两个轻量级
- 缺点:需要自己管理线程的生命周期,线程同步。线程同步对数据的加锁会有一定的系统开销
NSThread的使用
1 |
|
第一个是实例方法,第二个是类方法
1 |
|
这两种方式的区别是:前一种一调用就会立即创建一个线程来做事情;而后一种虽然你alloc了也init了,但是要直到我们手动调用 start
启动线程时才会真正去创建线程。这种延迟实现思想在很多跟资源相关的地方都有用到。后一种方式我们还可以在启动线程之前,对线程进行配置,比如设置stack大小,线程优先级。
还有一种间接的方式,更加方便,我们甚至不需要显式编写NSThread相关代码。那就是利用NSObject的类方法 performSelectorInBackground:withObject:
来创建一个线程:
1 |
|
其效果与NSThread的 detachNewThreadSelector:toTarget:withObject:
是一样的
线程同步
线程的同步方法跟其他系统下类似,我们可以用原子操作,可以用mutex,lock等。
iOS的原子操作函数是以OSAtomic开头的,比如:OSAtomicAdd32, OSAtomicOr32等等。这些函数可以直接使用,因为它们是原子操作。
iOS中的mutex对应的是NSLock,它遵循 NSLooking协议,我们可以使用lock, tryLock, lockBeforeData:来加锁,用unLock来解锁。使用示例:
1 |
|
我们可以使用指令 @synchronized 来简化 NSLock的使用,这样我们就不必显示编写创建NSLock,加锁并解锁相关代码。
1 |
|
还有其他的一些锁对象,比如:循环锁NSRecursiveLock,条件锁NSConditionLock,分布式锁NSDistributedLock等等,在这里就不一一介绍了。
用NSCodition同步执行的顺序
NSCodition是一种特殊类型的锁,我们可以用它来同步操作执行的顺序。它与mutex的区别在于更加精准,等待某个NSCondtion的线程一直被lock,直到其他线程给那个condition发送了信号。下面我们来看使用示例:
1 |
|
线程间通信
线程在运行过程中,可能需要与其它线程进行通信。我们可以使用 NSObject 中的一些方法:
1 |
|
如在我们在某个线程中下载数据,下载完成之后要通知主线程中更新界面等等,可以使用如下接口:
1 |
|
隐式调用
用NSObject的类方法performSelectorInBackground:withObject:
创建一个线程:
1 |
|
NSOperationQueue和NSOperation
多线程编程是防止主线程堵塞,增加运行效率等等的最佳方法。而原始的多线程方法存在很多的毛病,包括线程锁死等。在Cocoa中,Apple提供了NSOperation这个类,提供了一个优秀的多线程编程方法。
NSOperationQueue会建立一个线程管理器,每个加入到线程operation会有序的执行。
用NSOperationQueue的过程:
- 建立一个NSOperationQueue的对象
- 建立一个NSOperation的对象
- 将operation加入到NSOperationQueue中
- release掉operation
本次介绍NSOperation的子集,简易方法的NSInvocationOperation:
1 |
|
一个NSOperationQueue操作队列,就相当于一个线程管理器,而非一个线程。因为你可以设置这个线程管理器内可以并行运行的的线程数量等等。下面是建立并初始化一个操作队列:
1 |
|
GCD
GCD是一个替代诸如NSThread, NSOperationQueue, NSInvocationOperation等技术的很高效和强大的技术,它看起来象就其它语言的闭包(Closure)一样,但苹果把它叫做blocks。
GCD的定义
简单GCD的定义有点象函数指针,差别是用 ^ 替代了函数指针的 * 号,如下所示:
1 |
|
但是大多数时候,我们通常使用内联的方式来定义它,即将它的程序块写在调用的函数里面,例如这样:
1 |
|
从上面大家可以看出,block有如下特点:
程序块可以在代码中以内联的方式来定义。 程序块可以访问在创建它的范围内的可用的变量。
系统提供的dispatch方法
为了方便地使用GCD,苹果提供了一些方法方便我们将block放在主线程 或 后台线程执行,或者延后执行。使用的例子如下:
1 |
|
后台运行
GCD的另一个用处是可以让程序在后台较长久的运行。在没有使用GCD时,当app被按home键退出后,app仅有最多5秒钟的时候做一些保存或清理资源的工作。但是在使用GCD后,app最多有10分钟的时间在后台长久运行。这个时间可以用来做清理本地缓存,发送统计数据等工作。
另外,GCD还有一些高级用法,例如让后台2个线程并行执行,然后等2个线程都结束后,再汇总执行结果。这个可以用dispatch_group, dispatch_group_async 和 dispatch_group_notify来实现,示例如下:
1 |
|
让程序在后台长久运行的示例代码如下:
1 |
|
NSOperationQueue与GCD的对比
对于NSOperationQueue和GCD应该用哪个,一般来说可以用编程界比较通用的原则来决定:
Always use the highest-level abstraction available to you, and drop down to lower-level abstractions when measurement shows that they are needed.
简意是:尽可能用更高级抽象的方法。但前面提到的*里的讨论里却分别说出了两者的优缺点:
NSOperation好处:
- 很容易设置两个NSOperation之间的依赖来让某一个操作在上一个操作完成后才执行
- 方便设置在同一时间运行的操作个数
您可以创建操作,支持在第一时间被取消
bandwidth-constrained queues that only run N operations at a time
- establishing dependencies between operations
- you can create operations that support being cancelled in the first place
GCD好处:
- NSOperation对象在创建或释放过程中会消耗明显的CPU资源
使用Blocks后代码比使用NSOperation更简洁
The NSOperation object allocation and deallocation process took a significant amount of CPU resources when dealing with small and frequent actions, like rendering an OpenGL ES frame to the screen. GCD blocks completely eliminated that overhead, leading to significant performance improvements.
- code is cleaner when using blocks than NSOperations.
当然,具体使用哪一个还是要看你的使用场合了。