多线程学习
多线程学习
每一个iOS应用(进程)运行都会有一个主线程(UI线程),UI上的更新推荐在主线程中去完成。多线程本身并不复杂,难点在于多个线程在其生命周期的管理,如线程的执行顺序、线程间的数据共享以及资源竞争等问题。
本文主要记录开发中常用的3种多线程模式:
- NSThread
- NSOperation
- GCD
一、NSThread
NSThread是一种轻量级的多线程开发模式,使用起来也比较简单主要通过一下两个方法来创建新线程
+ (void)detachNewThreadSelector:(SEL)selector toTarget:(id)target withObject:(nullable id)argument; - (instancetype)initWithTarget:(id)target selector:(SEL)selector object:(nullable id)argument NS_AVAILABLE(10_5, 2_0); [NSThread start];
线程状态分为3类:正在运行、已经完成、正在取消;可以用cancel来改变线程状态,注意这并没有真正的终止线程,除非调用其静态方法[NSTread exist]来终止线程。
线程优先级的范围是0~1,值越大优先级越高,线程默认值为0.5
NSObject分类 NSThreadPerformAdditions提供线程UI更新的主要方法如下:
//主线程才能更新UI [self performSelectorOnMainThread:@selector(updateImageView:) withObject:imagedata waitUntilDone:YES]; - (void)performSelector:(SEL)aSelector onThread:(NSThread *)thr withObject:(nullable id)arg waitUntilDone:(BOOL)wait NS_AVAILABLE(10_5, 2_0); - (void)performSelectorInBackground:(SEL)aSelector withObject:(nullable id)arg NS_AVAILABLE(10_5, 2_0);
总结
- 使用简单,但线程的执行顺序很难控制;
- 需要自己管理线程的生命周期,一个任务创建一个线程占用系统开销;
二、NSOperation
类似C#中的线程池,创建NSOperation放入NSOperationQueue队列中一次启动执行。NSOperation更加容易管理线程总数和线程之间的依赖关系。
NSOperation有两个子类用于创建线程:NSInvocationOperation 和 NSBlockOperation。
// NSInvocationOperation创建 //创建NSOperation NSInvocationOperation *operation = [[NSInvocationOperation alloc]initWithTarget:self selector:@selector() object:nil]; //创建操作队列 NSOperationQueue *queue = [[NSOperationQueue alloc]init]; //加入到队列,开启一个线程执行队列中的线程 [queue addOperation:operation]; //NSBlockOperation创建 NSOperationQueue *queue = [[NSOperationQueue alloc]init]; //1、创建 queue.maxConcurrentOperationCount = 5; NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^{ }]; [queue addOperation:operation]; //2、直接代码块加入队列 [queue addOperationWithBlock:^{ }];
总结
- NSOperation可以设置最大并发数目;
- 可以设置依赖线程。假设线程A依赖线程B,操作队列启动后就会首先执行B再执行A,[operationA addDependency:OperationB]
三、GCD(Grand Central Dispatch)
基于C语言开发的一套多线程开发机制,也是苹果推荐的多线程开发方法。这种机制最显著的优点就是他对多核运算更佳有效。
GCD有一个类似NSOperationQueue的队列,分为:
- 串行队列:只有一个线程,加入到队列的操作依次执行。
- 并行队列:多个线程,将操作任务安排在可用的处理器上,同时保证先进来的任务先处理。
- 主队列:用于在主线程上执行的任务队列,如UI更新。
//创建一个串行异步队列 dispatch_queue_t serialQueue = dispatch_queue_create(@"queuename", DISPATCH_QUEUE_SERIAL); dispatch_async(serialQueue, ^{ }); //UI更新 dispatch_queue_t mainQueue = dispatch_get_main_queue(); dispatch_sync(mainQueue, ^{ });
并发队列同样是使用dispatch_queue_create()方法创建,只是最后一个参数指定为DISPATCH_QUEUE_CONCURRENT进行创建,但是在实际开发中我们通常不会重新创建一个并发队列而是使用dispatch_get_global_queue()方法取得一个全局的并发队列。
//创建一个全局并行异步队列 dispatch_queue_t global = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); dispatch_async(global, ^{ });
GCD其他常用的执行方法:
- dispatch_apply():重复执行某个任务,但是注意这个方法没有办法异步执行(为了不阻塞线程可以使用dispatch_async()包装一下再执行)。
- dispatch_once():单次执行一个任务,此方法中的任务只会执行一次,重复调用也没办法重复执行(单例模式中常用此方法)。
- dispatch_time():延迟一定的时间后执行。
- dispatch_barrier_async():使用此方法创建的任务首先会查看队列中有没有别的任务要执行,如果有,则会等待已有任务执行完毕再执行;同时在此方法后添加的任务必须等待此方法中任务执行后才能执行。(利用这个方法可以控制执行顺序,例如前面先加载最后一张图片的需求就可以先使用这个方法将最后一张图片加载的操作添加到队列,然后调用dispatch_async()添加其他图片加载任务)
- dispatch_group_async():实现对任务分组管理,如果一组任务全部完成可以通过6. dispatch_group_notify()方法获得完成通知(需要定义dispatch_group_t作为分组标识)。
GCD的锁机制
说到多线程就不得不提多线程中的锁机制,多线程操作过程中往往多个线程是并发执行的,同一个资源可能被多个线程同时访问,造成资源抢夺,这个过程中如果没有锁机制往往会造成重大问题。
NSLock : 同步锁NSLock来解决,使用时把需要加锁的代码(以后暂时称这段代码为”加锁代码“)放到NSLock的lock和unlock之间,一个线程A进入加锁代码之后由于已经加锁,另一个线程B就无法访问,只有等待前一个线程A执行完加锁代码后解锁,B线程才能访问加锁代码。
@synchronized代码块:日常开发中也更推荐使用此方法。首先选择一个对象作为同步对象(一般使用self),然后将”加锁代码”(争夺资源的读取、修改代码)放到代码块中。@synchronized中的代码执行时先检查同步对象是否被另一个线程占用,如果占用该线程就会处于等待状态,直到同步对象被释放。
//线程同步 @synchronized(self){ if (_imageNames.count>0) { name=[_imageNames lastObject]; [NSThread sleepForTimeInterval:0.001f]; [_imageNames removeObject:name]; } }
GCD信号机制
在GCD中提供了一种信号机制,也可以解决资源抢占问题(和同步锁的机制并不一样)。GCD中信号量是dispatch_semaphore_t类型,支持 信号通知 和 信号等待。每当发送一个信号通知,则信号量+1;每当发送一个等待信号时信号量-1,;如果信号量为0则信号会处于等待状态,直到信号量大于0开始执行。根据这个原理我们可以初始化一个信号量变量,默认信号量设置为1,每当有线程进入“加锁代码”之后就调用信号等待命令(此时信号量为0)开始等待,此时其他线程无法进入,执行完后发送信号通知(此时信号量为1),其他线程开始进入执行,如此一来就达到了线程同步目的。
dispatch_semaphore_t _semaphore;//定义一个信号量 //初始化信号量参数 _semaphore = dispatch_semaphore_create(1); //信号等待,第二个参数:等待时间 dispatch_semaphore_wait(_semaphore, DISPATCH_TIME_FOREVER); if (_imageNames.count>0) { name=[_imageNames lastObject]; [_imageNames removeObject:name]; } //信号通知 dispatch_semaphore_signal(_semaphore);