iOS 多线程开发 (概念与API简介)

   本文主要是我对多线程的总结,当是给自己的个复习,希望也可以给别人一些参考。


第一部分:概念及总结

  还是从概念开始,先介绍一些概念性的东西,虽然简单,但是理解的深刻程度决定了是否能正解使用多线程的,还有用得有多好。

  线程:线程是程序中一个单一的顺序控制流程,是线程中的一个实体,一个标准的线程由线程ID,当前指令指针(PC),寄存器集合和堆栈组成。

它有以下属性:

  1、轻型实体 。2、独立调度和分派的基本单位。3、可并发执行。4、共享进程资源。

接下来就是在iOS中能够使用的四种多线程编程方式:

  (一)pthread

  这里并不会很详细的去讲解它,百度上是这么说的:

  POSIX线程(POSIX threads),简称Pthreads,是线程的POSIX标准。该标准定义了创建和操纵线程的一整套API。在类Unix操作系统(Unix、Linux、Mac OS X等)中,都使用Pthreads作为操作系统的线程。Windows操作系统也有其移植版pthreads-win32。

   iOS开发中很少直接使用它。要使用它就得使用pthread.h中的一系列C接口,使用那些底层接口去创建,调度,管理线程,相对复杂,并且要考虑的问题会很多。

  优点:跨平台,轻量级,可定制性强。

  缺点:接口使用起来相对得杂,如要自己处理线程创建,调度释放等。

  (二)NSThread

 它是苹果对pthread的进一步封装,并且是面向对象的。封装后我们面对的是线程对象,而且它的接口也更好理解,使用起来更加直观和方便。

 优点:面向对象,接口都是oc方法,使用方便,适应iOS 上开发。

 缺点:相对还是需要自己处理线程创建,调度释放。

   (三)Grand Central Dispatch(GCD).

 它是苹果开发的一个多核编程的解决方法。

  优点:使用GCD,它会自动管理线程的生命周期(创建线程、调度任务、销毁线程),完全不需要我们管理,我们关注重心只要在自己的任务,并把任务投入到相应的队列。GCD使用的也是 c语言,结合使用了 Block,使得使用起来更加方便,而且灵活。性能相对接进底层。

    缺点:如果虽要有相互依赖,优先级等,相比NSOperation需要自己写更多代码控制

(四)NSOperation

  NSOperation在iOS 4.x以后就是GCD封装的,它是GCD面向对象的封装。

 优点:并且提供有用且线程安全的建立状态,优先级,依赖和取消等操作。

   缺点:性能比GCD稍微差一点。

对比小结:PThread、NSThread、 GCD、 NSOperation 抽象封装度层次从低到高,抽象封装度越高使用越简单。PThread与NSThread高可定制,但相对复杂难用。完全可以按照自己的想法实现一个类GCD这样多线程解决方案。GCD、 NSOperation是为简化iOS开发者多线程开发而生的解决方法,它可以使我们把精力大部分投到自己业务的任务要做什么,而线程的创建,释放,调度是不需要我们去关心的。而且还提供了方便的方法让我们去管理线程的一些状态,依赖,优先级及通信。


第二部分:具体接口解析及常用方式

(一)NSThread:

  主要有两种方式去使用NSThread(另外还有一种继承NSThread的方式可看官方文档)。

     (1)使用它的类方法detachNewThreadSelector:toTarget:withObject:去创建一个新的线程,使用这方式,线程结束后它所关联的资源就会被系统自动回收。

[NSThread detachNewThreadSelector:@selector(myThreadMainMethod:) toTarget:self withObject:nil];

     (2)创建一个NSThread对象并且调用它的start方法。

NSThread* myThread = [[NSThread alloc] initWithTarget:self
                                        selector:@selector(myThreadMainMethod:)
                                        object:nil];
[myThread start];  // 实际创建线程

设置线程优先级可以使用这个两属性

@property double threadPriority NS_AVAILABLE(10_6, 4_0); // To be deprecated; use qualityOfService below

@property NSQualityOfService qualityOfService NS_AVAILABLE(10_10, 8_0); // read-only after the thread is started

当然少不了平常使用的挺多也很便捷的NSObject (NSThreadPerformAdditions)里的方法, 不进行解释了

- (void)performSelectorOnMainThread:(SEL)aSelector withObject:(nullable id)arg waitUntilDone:(BOOL)wait modes:(nullable NSArray<NSString *> *)array;
- (void)performSelectorOnMainThread:(SEL)aSelector withObject:(nullable id)arg waitUntilDone:(BOOL)wait;
    // equivalent to the first method with kCFRunLoopCommonModes

- (void)performSelector:(SEL)aSelector onThread:(NSThread *)thr withObject:(nullable id)arg waitUntilDone:(BOOL)wait modes:(nullable NSArray<NSString *> *)array NS_AVAILABLE(10_5, 2_0);
- (void)performSelector:(SEL)aSelector onThread:(NSThread *)thr withObject:(nullable id)arg waitUntilDone:(BOOL)wait NS_AVAILABLE(10_5, 2_0);
    // equivalent to the first method with kCFRunLoopCommonModes
- (void)performSelectorInBackground:(SEL)aSelector withObject:(nullable id)arg NS_AVAILABLE(10_5, 2_0);

但是使用这performSelector:onThread:withObject:wait:modes及performSelector:onThread:withObject:wait:modes时要注意如果指派的线程并非主线徎时,要确保此线程的runloop被启动,否则是不会被调用到selector的。

(二)Grand Central Dispatch

GCD中有2个核心概念

(1)队列:用来存放任务

(2)任务:执行什么操作

队列:

使用dispatch_get_main_queue函数获取主队列,分派在这个队列的任务都会被投放到主线程去执行。

使用dispatch_get_global_queue函数获取全局队列,分派在这个队列的任务会被投放到各个线程中并发执行。

使用dispatch_queue_create函数创建一个新的队列,即可以创建并发也可以创建串行队列,分派在这个队列的任务会在子线程执行。

任务分派,有两种方式

异步分派:分派后不会等待任务执行,而是马上返回,继续当前线程的执行流。

同步分派:分派后等待任务执行完,才回到当前线程执行流继续执行。

异步分派的方法有:

void dispatch_async( dispatch_queue_t queue, dispatch_block_t block);

void dispatch_async_f( dispatch_queue_t queue, void *context, dispatch_function_t work);

void dispatch_after( dispatch_time_t when, dispatch_queue_t queue, dispatch_block_t block);

void dispatch_after_f( dispatch_time_t when, dispatch_queue_t queue, void *context, dispatch_function_t work);

同步分派的方法有:

void dispatch_sync( dispatch_queue_t queue, dispatch_block_t block);

void dispatch_sync_f( dispatch_queue_t queue, void *context, dispatch_function_t work);

对于有_f后缀和没有_f后缀的区别只是任务的实现是在Block中还是函数指针指向的函数。

个人理解:GCD 的队列与线程的关系:分派到主队列的任务都会被GCD投放到主线程中执行,分派到全局并发队列的任务会被投放到各个子线程并发执行,GCD决定是否会创建新的线程去执行这些任务。分派到自己创建的串行队列的任务会被单一线程处理,同步分派会的任务在当前线程马上执行。异步分派则不会马上执行,而且也是由GCD和是否分派到当前线程的队列决定是否会创建新的线程去执行这些任务。上述两处地方说到GCD决定是否会创建新的线程正使用它的优势所在,GCD会根据系统资源,当前线程数量,任务数量等做一个权衡,使线程数,CPU使用等达到一个最优值。

使用

void dispatch_once( dispatch_once_t *predicate, dispatch_block_t block);

void dispatch_once_f( dispatch_once_t *predicate, void *context, dispatch_function_t function);

让程序只执行一次block中的任务,通常用于创建单例。

使用void dispatch_group_async( dispatch_group_t group, dispatch_queue_t queue, dispatch_block_t block);关联一组任务

再使用void dispatch_group_notify( dispatch_group_t group, dispatch_queue_t queue, dispatch_block_t block);可以得到关联的所有任务都完成的通知。

GCD中有三个函数是semaphore的操作,分别是:

  dispatch_semaphore_create   创建一个semaphore

  dispatch_semaphore_signal   发送一个信号

  dispatch_semaphore_wait    等待信号

通过使用这三个接口,可以控制线程的并发数量,也可以实现线程的同步操作,也可以实现类似锁的功能。

下面内容翻译自官方文档

一个dispatch barrier 允许在一个并发队列中创建一个同步点。当在并发队列中遇到一个barrier, 他会延迟执行barrier的block,等待所有在barrier之前提交的blocks执行结束。 这时,barrier block自己开始执行。 之后, 队列继续正常的执行操作。

调用这个函数总是在barrier block被提交之后立即返回,不会等到block被执行。当barrier block到并发队列的最前端,他不会立即执行。相反,队列会等到所有当前正在执行的blocks结束执行。到这时,barrier才开始自己执行。所有在barrier block之后提交的blocks会等到barrier block结束之后才执行。

这里指定的并发队列应该是自己通过dispatch_queue_create函数创建的。如果你传的是一个串行队列或者全局并发队列,这个函数等同于dispatch_async函数。

 (三)NSOperation & NSOperationQueue

NSOperation它只定义了一些接口,并不能直接使用,因为直接使用它并不能向它添加所要执行的任务。

使用 NSOperation的方式有两种,

一种是用定义好的两个子类:

NSInvocationOperation NSBlockOperation。这两个类的api也比较简单,不做介绍。

另一种是自己继承NSOperation,实现main或者start方法。

使用main方法非常简单,开发者不需要管理一些状态属性(例如 isExecuting isFinished)当 main 方法返回的时候,这个 operation 就结束了。这种方式使用起来非常简单,但是灵活性相对重写 start 来说要少一些, 因为main方法执行完就认为operation结束了,所以一般可以用来执行同步任务。

@implementation MyOperation
- (void)main
{
    // 任务代码 ...
}
@end

如果你希望拥有更多的控制权,或者想在一个操作中可以执行异步任务,那么就重写start方法, 但是注意:这种情况下,你必须手动管理操作的状态, 只有当发送finish的 KVO 消息时,才认为是 operation 结束

@implementation MyOperation
- (void)start
{
  self.isExecuting = YES;
    // 任务代码 ...
}
- (void)finish //异步回调
{
  self.isExecuting = NO;
  self.isFinished = YES;
}
@end

为了让操作队列能够捕获到操作的改变,需要将状态的属性以配合KVO的方式进行实现。如果你不使用它们默认的 setter 来进行设置的话,你就需要在合适的时候发送合适的KVO消息。

当某个NSOperation对象依赖于其它NSOperation对象的完成时,就可以通过addDependency方法添加一个或者多个依赖的对象,只有所有依赖的对象都已经完成操作,当前NSOperation对象才会开始执行操作。另外,通过removeDependency方法来删除依赖对象。

- (void)addDependency:(NSOperation *)op;
- (void)removeDependency:(NSOperation *)op;

NSOperationQueue

一个NSOperation对象可以通过调用start方法来执行任务,默认是同步执行的。也可以将NSOperation添加到一个NSOperationQueue(操作队列)中去执行,而且是异步执行的。

添加NSOperationNSOperationQueue中三种方式

- (void)addOperation:(NSOperation *)op;
- (void)addOperations:(NSArray<NSOperation *> *)ops waitUntilFinished:(BOOL)wait;
- (void)addOperationWithBlock:(void (^)(void))block;

NSOperationQueue还可以支持设置最大并发数,当并发数为1时相当于创建了一个串行队列。

@property NSInteger maxConcurrentOperationCount;

还有可以暂停、继续、取消队列中任务,设置优先级等接口

@property (getter=isSuspended) BOOL suspended;
@property NSQualityOfService qualityOfService;- (void)cancelAllOperations;

结束语:

  以上基本绍了iOS多程编程的多种方式,具体使用哪种方式需要根据实际场境去选择。AFNetworking中AFURLConnectionOperation和AFHTTPRequestOperation把GCD和NSOperation结合使用,是一个非常好的学习例子。

 

 

作者:xianmingchen
出处:http://www.cnblogs.com/chenxianming/
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任权利。
原文地址:https://www.cnblogs.com/chenxianming/p/5575443.html