手机APP开发 2016-11-21
每一个iOS应用(进程)运行都会有一个主线程(UI线程),UI上的更新推荐在主线程中去完成。多线程本身并不复杂,难点在于多个线程在其生命周期的管理,如线程的执行顺序、线程间的数据共享以及资源竞争等问题。
本文主要记录开发中常用的3种多线程模式:
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);
总结
类似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:^{ }];
总结
基于C语言开发的一套多线程开发机制,也是苹果推荐的多线程开发方法。这种机制最显著的优点就是他对多核运算更佳有效。
GCD有一个类似NSOperationQueue的队列,分为:
//创建一个串行异步队列 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其他常用的执行方法:
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);