多线程总结
//1、NSThread /** 优点:NSThread 比其他两个轻量级。 缺点:需要自己管理线程的生命周期,线程同步,线程同步时对数据的加锁会有一定的系统开销。 cocoa给我提供了两种方法生成线程: 1: - (id)initWithTarget:(id)target selector:(SEL)selector object:(id)argument NSThread* thread = [[NSThread alloc] initWithTarget:self selector:@selector(do:) object:nil]; [thread start]; 2: + (void)detachNewThreadSelector:(SEL)aSelector toTarget:(id)aTarget withObject:(id)anArgument [NSThread detachNewThreadSelector:@selector(do:) toTarget:self withObject:nil]; 3:cocoa中的一些函数也会单独开辟一个线程执行我们的操作如: - (id)performSelector:(SEL)aSelector; - (id)performSelector:(SEL)aSelector withObject:(id)object; - (void)performSelector:(SEL)aSelector withObject:(id)anArgument afterDelay:(NSTimeInterval)delay inModes:(NSArray *)modes; - (void)performSelectorInBackground:(SEL)aSelector withObject:(id)argNS_AVAILABLE(10_5, 2_0); //对于多线程操作建议把线程操作放到@autoreleasepool中 线程状态分为isExecuting(正在执行)、isFinished(已经完成)、isCancellled(已经取消)三种 */ //2、Cocoa NSOperation (使用NSOperation和NSOperationQueue) /** 优点:不需要关心线程管理,数据同步的事情。 Cocoa Operation 相关的类是 NSOperation ,NSOperationQueue。NSOperation是个抽象类,使用它必须用它的子类,可以实现它或者使用它定义好的两个子类:NSInvocationOperation 和 NSBlockOperation。创建NSOperation子类的对象,把对象添加到NSOperationQueue队列里执行,我们会把我们的执行操作放在NSOperation中main函数中。 创建NSInvocationOperation一个调用操作 object:调用方法参数 NSInvocationOperation *invocationOperation=[[NSInvocationOperation alloc]initWithTarget:self selector:@selector(loadImage) object:nil]; //创建完NSInvocationOperation对象并不会调用,它由一个start方法启动操作,但是注意如果直接调用start方法,则此操作会在主线程中调用,一般不会这么操作,而是添加到NSOperationQueue中 // [invocationOperation start]; //创建操作队列 NSOperationQueue *operationQueue=[[NSOperationQueue alloc]init]; //注意添加到操作队后,队列会开启一个线程执行此操作 [operationQueue addOperation:invocationOperation]; //创建NSBlockOperation一个调用操作 //方法1:创建操作块添加到队列 // //创建多线程操作 // NSBlockOperation *blockOperation=[NSBlockOperation blockOperationWithBlock:^{ // [self loadImage:[NSNumber numberWithInt:i]]; // }]; // //创建操作队列 // // [operationQueue addOperation:blockOperation]; //方法2:直接使用操队列添加操作 [operationQueue addOperationWithBlock:^{ [self loadImage:[NSNumber numberWithInt:i]]; }]; 1. 使用NSBlockOperation方法,所有的操作不必单独定义方法,同时解决了只能传递一个参数的问题。 2. 调用主线程队列的addOperationWithBlock:方法进行UI更新,不用再定义一个参数实体(之前必须定义一个KCImageData解决只能传递一个参数的问题)。 3. 使用NSOperation进行多线程开发可以设置最大并发线程,有效的对线程进行了控制(上面的代码运行起来你会发现打印当前进程时只有有限的线程被创建,如上面的代码设置最大线程数为5,则图片基本上是五个一次加载的)。 */ //3、GCD (Grand Central Dispatch):http://blog.csdn.net/springpp1990/article/details/12451913 http://www.cnblogs.com/wendingding/p/3806821.html /** //GCD是苹果公司为多核的并行运算提出的解决方案 GCD会自动利用更多的CPU内核(比如双核、四核) GCD会自动管理线程的生命周期(创建线程、调度任务、销毁线程) 程序员只需要告诉GCD想要执行什么任务,不需要编写任何线程管理代码 GCD中有2个核心概念 (1)任务:执行什么操作 (2)队列:用来存放任务 说明:把右边的参数(任务)提交给左边的参数(队列)进行执行。 (1)用同步的方式执行任务 dispatch_sync(dispatch_queue_t queue, dispatch_block_t block); 参数说明: queue:队列 block:任务 (2)用异步的方式执行任务 dispatch_async(dispatch_queue_t queue, dispatch_block_t block); 2.同步和异步的区别 同步:在当前线程中执行 异步:在另一条线程中执行 1.队列的类型 GCD的队列可以分为2大类型 (1)并发队列(Concurrent Dispatch Queue) 可以让多个任务并发(同时)执行(自动开启多个线程同时执行任务)并发功能只有在异步(dispatch_async)函数下才有效 (2)串行队列(Serial Dispatch Queue) 让任务一个接着一个地执行(一个任务执行完毕后,再执行下一个任务) 2.补充说明 有4个术语比较容易混淆:同步、异步、并发、串行 同步和异步决定了要不要开启新的线程 同步:在当前线程中执行任务,不具备开启新线程的能力 异步:在新的线程中执行任务,具备开启新线程的能力 并发和串行决定了任务的执行方式 并发:多个任务并发(同时)执行 串行:一个任务执行完毕后,再执行下一个任务 3.串行队列 GCD中获得串行有2种途径 (1)使用dispatch_queue_create函数创建串行队列 dispatch_queue_t dispatch_queue_create(const char *label, dispatch_queue_attr_t attr); // 队列名称, 队列属性,一般用NULL即可 示例: dispatch_queue_t queue = dispatch_queue_create("wendingding", NULL); // 创建 dispatch_release(queue); // 非ARC需要释放手动创建的队列 (2)使用主队列(跟主线程相关联的队列) 主队列是GCD自带的一种特殊的串行队列,放在主队列中的任务,都会放到主线程中执行 使用dispatch_get_main_queue()获得主队列 示例: dispatch_queue_t queue = dispatch_get_main_queue(); 4.并发队列 GCD默认已经提供了全局的并发队列,供整个应用使用,不需要手动创建 使用dispatch_get_global_queue函数获得全局的并发队列 dispatch_queue_t dispatch_get_global_queue(dispatch_queue_priority_t priority,unsigned long flags); // 此参数暂时无用,用0即可 示例: 这个参数是留给以后用的,暂时用不上,传个0。 第一个参数为优先级,这里选择默认的。获取一个全局的默认优先级的并发队列。 dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); // 获得全局并发队列 小结 说明:同步函数不具备开启线程的能力,无论是什么队列都不会开启线程;异步函数具备开启线程的能力,开启几条线程由队列决定(串行队列只会开启一条新的线程,并发队列会开启多条线程)。 同步函数 (1)并发队列:不会开线程 (2)串行队列:不会开线程 异步函数 (1)并发队列:能开启N条线程 (2)串行队列:开启1条线程 补充: 凡是函数中,各种函数名中带有create\copy\new\retain等字眼,都需要在不需要使用这个数据的时候进行release。 GCD的数据类型在ARC的环境下不需要再做release。 CF(core Foundation)的数据类型在ARC环境下还是需要做release。 异步函数具备开线程的能力,但不一定会开线程 1. dispatch_apply():重复执行某个任务,但是注意这个方法没有办法异步执行(为了不阻塞线程可以使用dispatch_async()包装一下再执行)。 2. dispatch_once():单次执行一个任务,此方法中的任务只会执行一次,重复调用也没办法重复执行(单例模式中常用此方法)。 3. dispatch_time():延迟一定的时间后执行。 4. dispatch_barrier_async():使用此方法创建的任务首先会查看队列中有没有别的任务要执行,如果有,则会等待已有任务执行完毕再执行;同时在此方法后添加的任务必须等待此方法中任务执行后才能执行。(利用这个方法可以控制执行顺序,例如前面先加载最后一张图片的需求就可以先使用这个方法将最后一张图片加载的操作添加到队列,然后调用dispatch_async()添加其他图片加载任务) 5. dispatch_group_async():实现对任务分组管理,如果一组任务全部完成可以通过dispatch_group_notify()方法获得完成通知(需要定义dispatch_group_t作为分组标识)。 需要注意的是lock和unlock之间的”加锁代码“应该是抢占资源的读取和修改代码,不要将过多的其他操作代码放到里面,否则一个线程执行的时候另一个线程就一直在等待,就无法发挥多线程的作用了:unlock,lock @synchronized代码块 NSCondition实现了NSLocking协议,所以它本身也有lock和unlock方法,因此也可以将它作为NSLock解决线程同步问题,此时使用方法跟NSLock没有区别,只要在线程开始时加锁,取得资源后释放锁即可:signal,unlock,wait,lock iOS中的其他锁 在iOS开发中,除了同步锁有时候还会用到一些其他锁类型,在此简单介绍一下: NSRecursiveLock :递归锁,有时候“加锁代码”中存在递归调用,递归开始前加锁,递归调用开始后会重复执行此方法以至于反复执行加锁代码最终造成死锁,这个时候可以使用递归锁来解决。使用递归锁可以在一个线程中反复获取锁而不造成死锁,这个过程中会记录获取锁和释放锁的次数,只有最后两者平衡锁才被最终释放。 NSDistributedLock:分布锁,它本身是一个互斥锁,基于文件方式实现锁机制,可以跨进程访问。 pthread_mutex_t:同步锁,基于C语言的同步锁机制,使用方法与其他同步锁机制类似。 */ //多线程总结 /** 1>无论使用哪种方法进行多线程开发,每个线程启动后并不一定立即执行相应的操作,具体什么时候由系统调度(CPU空闲时就会执行)。 2>更新UI应该在主线程(UI线程)中进行,并且推荐使用同步调用,常用的方法如下: • - (void)performSelectorOnMainThread:(SEL)aSelector withObject:(id)arg waitUntilDone:(BOOL)wait (或者-(void)performSelector:(SEL)aSelector onThread:(NSThread *)thr withObject:(id)arg waitUntilDone:(BOOL) wait;方法传递主线程[NSThread mainThread]) • [NSOperationQueue mainQueue] addOperationWithBlock: • dispatch_sync(dispatch_get_main_queue(), ^{}) 3>NSThread适合轻量级多线程开发,控制线程顺序比较难,同时线程总数无法控制(每次创建并不能重用之前的线程,只能创建一个新的线程)。 4>对于简单的多线程开发建议使用NSObject的扩展方法完成,而不必使用NSThread。 5>可以使用NSThread的currentThread方法取得当前线程,使用 sleepForTimeInterval:方法让当前线程休眠。 6>NSOperation进行多线程开发可以控制线程总数及线程依赖关系。 7>创建一个NSOperation不应该直接调用start方法(如果直接start则会在主线程中调用)而是应该放到NSOperationQueue中启动。 8>相比NSInvocationOperation推荐使用NSBlockOperation,代码简单,同时由于闭包性使它没有传参问题。 9>NSOperation是对GCD面向对象的ObjC封装,但是相比GCD基于C语言开发,效率却更高,建议如果任务之间有依赖关系或者想要监听任务完成状态的情况下优先选择NSOperation否则使用GCD。 10>在GCD中串行队列中的任务被安排到一个单一线程执行(不是主线程),可以方便地控制执行顺序;并发队列在多个线程中执行(前提是使用异步方法),顺序控制相对复杂,但是更高效。 11>在GDC中一个操作是多线程执行还是单线程执行取决于当前队列类型和执行方法,只有队列类型为并行队列并且使用异步方法执行时才能在多个线程中执行(如果是并行队列使用同步方法调用则会在主线程中执行)。 12>相比使用NSLock,@synchronized更加简单,推荐使用后者。 */