iOS沉思录NSThreadGCDNSOperation多线程编程总结.docx
- 文档编号:25025873
- 上传时间:2023-06-04
- 格式:DOCX
- 页数:22
- 大小:39.04KB
iOS沉思录NSThreadGCDNSOperation多线程编程总结.docx
《iOS沉思录NSThreadGCDNSOperation多线程编程总结.docx》由会员分享,可在线阅读,更多相关《iOS沉思录NSThreadGCDNSOperation多线程编程总结.docx(22页珍藏版)》请在冰豆网上搜索。
iOS沉思录NSThreadGCDNSOperation多线程编程总结
【iOS沉思录】NSThread、GCD、NSOperation多线程编程总结
OC中的多线程
OC中多线程根据封装程度可以分为三个层次:
NSThread、GCD和NSOperation,另外由于OC兼容C语言,因此仍然可以使用C语言的POSIX接口来实现多线程,只需引入相应的头文件:
#include
NSThread
NSThread是封装程度最小最轻量级的,使用更灵活,但要手动管理线程的生命周期、线程同步和线程加锁等,开销较大;
NSThread的基本使用比较简单,可以动态创建初始化NSThread对象,对其进行设置然后启动;也可以通过NSThread的静态方法快速创建并启动新线程;此外NSObject基类对象还提供了隐式快速创建NSThread线程的performSelector系列类别扩展工具方法;NSThread还提供了一些静态工具接口来控制当前线程以及获取当前线程的一些信息。
下面以在一个UIViewController中为例展示NSThread的使用方法:
-(void)viewDidLoad{
[superviewDidLoad];
/**NSThread静态工具方法**/
/*1是否开启了多线程*/
BOOLisMultiThreaded=[NSThreadisMultiThreaded];
/*2获取当前线程*/
NSThread*currentThread=[NSThreadcurrentThread];
/*3获取主线程*/
NSThread*mainThread=[NSThreadmainThread];
NSLog(@"inthread");
/*4睡眠当前线程*/
/*4.1线程睡眠5s钟*/
[NSThreadsleepForTimeInterval:
5];
/*4.2线程睡眠到指定时间,效果同上*/
[NSThreadsleepUntilDate:
[NSDatedateWithTimeIntervalSinceNow:
5]];
/*5退出当前线程,注意不要在主线程调用,防止主线程被kill掉*/
//[NSThreadexit];
NSLog(@"mainthread");
/**NSThread线程对象基本创建,target为入口函数所在的对象,selector为线程入口函数**/
/*1线程实例对象创建与设置*/
NSThread*newThread=[[NSThreadalloc]initWithTarget:
selfselector:
@selector(run)object:
nil];
/*设置线程优先级threadPriority(0~1.0),即将被抛弃,将使用qualityOfService代替*/
newThread.threadPriority=1.0;
newThread.qualityOfService=NSQualityOfServiceUserInteractive;
/*开启线程*/
[newThreadstart];
/*2静态方法快速创建并开启新线程*/
[NSThreaddetachNewThreadSelector:
@selector(run)toTarget:
selfwithObject:
nil];
[NSThreaddetachNewThreadWithBlock:
^{
NSLog(@"blockrun...");
}];
/**NSObejct基类隐式创建线程的一些静态工具方法**/
/*1在当前线程上执行方法,延迟2s*/
[selfperformSelector:
@selector(run)withObject:
nilafterDelay:
2.0];
/*2在指定线程上执行方法,不等待当前线程*/
[selfperformSelector:
@selector(run)onThread:
newThreadwithObject:
nilwaitUntilDone:
NO];
/*3后台异步执行函数*/
[selfperformSelectorInBackground:
@selector(run)withObject:
nil];
/*4在主线程上执行函数*/
[selfperformSelectorOnMainThread:
@selector(run)withObject:
nilwaitUntilDone:
NO];
}
-(void)run{
NSLog(@"run...");
}
GCD大中央调度
GCD(GrandCentralDispatch),又叫大中央调度,对线程操作进行了封装,加入了很多新的特性,内部进行了效率优化,提供了简洁的C语言接口,使用更加简单高效,也是苹果推荐的方式。
同步dispatch_sync与异步dispatch_async任务派发
串行队列与并发队列dispatch_queue_t
dispatch_once_t只执行一次
dispatch_after延后执行
dispatch_group_t组调度
两个关键概念
串行与并发(Serial和Concurrent):
这个概念在创建操作队列的时候有宏定义参数,用来指定创建的是串行队列还是并行队列。
串行指的是队列内任务一个接一个的执行,任务之间要依次等待不可重合,且添加的任务按照先进先出FIFO的顺序执行,但并不是指这就是单线程,只是同一个串行队列内的任务需要依次等待排队执行避免出现竞态条件,但仍然可以创建多个串行队列并行的执行任务,也就是说,串行队列内是串行的,串行队列之间仍然是可以并行的,同一个串行队列内的任务的执行顺序是确定的(FIFO),且可以创建任意多个串行队列;
并行指的是同一个队列先后添加的多个任务可以同时并列执行,任务之间不会相互等待,且这些任务的执行顺序执行过程不可预测。
同步和异步任务派发(Synchronous和Asynchronous):
GCD多线程编程时经常会使用dispatch_async和dispatch_sync函数往指定队列中添加任务块,区别就是同步和异步。
同步指的是阻塞当前线程,要等添加的耗时任务块block完成后,函数才能返回,后面的代码才可以继续执行。
如果在主线上,则会发生阻塞,用户会感觉应用不响应,这是要避免的。
而有时需要使用同步任务的原因是想保证先后添加的任务要按照编写的逻辑顺序依次执行;异步指的是将任务添加到队列后函数立刻返回,后面的代码不用等待添加的任务完成返回即可继续执行。
dispatch_sync与dispatch_async
通过下面的代码比较异步和同步任务的区别:
/*1.提交异步任务*/
NSLog(@"开始提交异步任务:
");
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0),^{
/*耗时任务...*/
[NSThreadsleepForTimeInterval:
10];
});
NSLog(@"异步任务提交成功!
");
/*2.提交同步任务*/
NSLog(@"开始提交同步任务:
");
dispatch_sync(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0),^{
/*耗时任务...*/
[NSThreadsleepForTimeInterval:
10];
});
NSLog(@"同步任务提交成功!
");
打印结果:
2017-02-2816:
01:
44.643SingleView[19100:
708069]开始提交异步任务:
2017-02-2816:
01:
44.643SingleView[19100:
708069]异步任务提交成功!
2017-02-2816:
01:
44.644SingleView[19100:
708069]开始提交同步任务:
2017-02-2816:
01:
54.715SingleView[19100:
708069]同步任务提交成功!
通过打印结果的时间可以看出,异步任务提交后立即就执行下一步打印提交成功了,不会阻碍当前线程,提交的任务会在后台去执行;而提交同步任务要等到提交的后台任务结束后才可以继续执行当前线程的下一步。
此处在主线程上添加的同步任务就会阻塞主线程,导致后面界面的显示要延迟,影响用户体验。
dispatch_queue_t
操作队列主要有两种,并发队列和串行队列,它们的区别上面已经提到,具体创建的方法很简单,要提供两个参数,一个是标记该自定义队列的唯一字符串,另一个是指定串行队列还是并发队列的宏参数:
/*创建一个并发队列*/
dispatch_queue_tconcurrent_queue=dispatch_queue_create("demo.gcd.concurrent_queue",DISPATCH_QUEUE_CONCURRENT);
/*创建一个串行队列*/
dispatch_queue_tserial_queue=dispatch_queue_create("demo.gcd.serial_queue",DISPATCH_QUEUE_SERIAL);
另外GCD还提供了几个常用的全局队列以及主队列,获取方法如下:
//获取主队列(在主线程上执行)
dispatch_queue_tmain_queue=dispatch_get_main_queue();
//获取不同优先级的全局队列(优先级从高到低)
dispatch_queue_tqueue_high=dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH,0);
dispatch_queue_tqueue_default=dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0);
dispatch_queue_tqueue_low=dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW,0);
dispatch_queue_tqueue_background=dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND,0);
dispatch_once_t
这个函数控制指定代码只会被执行一次,常用来实现单例模式,这里以单例模式实现的模板代码为例展示dispatch_once_t的用法,其中的实例化语句只会被执行一次:
+(instancetype*)sharedInstance{
staticdispatch_once_tonce=0;
staticidsharedInstance=nil;
dispatch_once(&once,^{
//只实例化一次
sharedInstance=[[selfalloc]init];
});
returnsharedInstance;
}
dispatch_after
通过该函数可以让要提交的任务在从提交开始后的指定时间后执行,也就是定时延迟执行提交的任务,使用方法很简单:
//定义延迟时间:
3s
dispatch_time_tdelay=dispatch_time(DISPATCH_TIME_NOW,(int64_t)(3.0*NSEC_PER_SEC));
dispatch_after(delay,dispatch_get_main_queue(),^{
//要执行的任务...
});
dispatch_group_t
组调度可以实现等待一组操作都完成后执行后续操作,典型的例子是大图片的下载,例如可以将大图片分成几块同时下载,等各部分都下载完后再后续将图片拼接起来,提高下载的效率。
使用方法如下:
dispatch_queue_tqueue=dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0);
dispatch_group_tgroup=dispatch_group_create();
dispatch_group_async(group,queue,^{/*操作1*/});
dispatch_group_async(group,queue,^{/*操作2*/});
dispatch_group_async(group,queue,^{/*操作3*/});
dispatch_group_notify(group,dispatch_get_main_queue(),^{
//后续操作...
});
同步代码到主线程
对于UI的更新代码,必须要在主线程上执行才会及时有效,当当前代码不在主线程时,需要将UI更新的部分代码单独同步到主线程,同步的方法有三种,可以使用NSThread类的performSelectorOnMainThread方法或者NSOperationQueue类的mainQueue主队列来进行同步,但推荐直接使用GCD方法:
dispatch_async(dispatch_get_main_queue(),^{
//UI更新代码...
});
NSOperation
NSOperation是基于GCD的一个抽象基类,将线程封装成要执行的操作,不需要管理线程的生命周期和同步,但比GCD可控性更强,例如可以加入操作依赖(addDependency)、设置操作队列最大可并发执行的操作个数(setMaxConcurrentOperationCount)、取消操作(cancel)等。
NSOperation作为抽象基类不具备封装我们的操作的功能,需要使用两个它的实体子类:
NSBlockOperation和NSInvocationOperation,或者继承NSOperation自定义子类。
NSBlockOperation和NSInvocationOperation用法的主要区别是:
前者执行指定的方法,后者执行代码块,相对来说后者更加灵活易用。
NSOperation操作配置完成后便可调用start函数在当前线程执行,如果要异步执行避免阻塞当前线程则可以加入NSOperationQueue中异步执行。
他们的简单用法如下:
-(void)viewDidLoad{
[superviewDidLoad];
/*NSInvocationOperation初始化*/
NSInvocationOperation*invoOpertion=[[NSInvocationOperationalloc]initWithTarget:
selfselector:
@selector(run)object:
nil];
[invoOpertionstart];
/*NSBlockOperation初始化*/
NSBlockOperation*blkOperation=[NSBlockOperationblockOperationWithBlock:
^{
NSLog(@"NSBlockOperation");
}];
[blkOperationstart];
}
-(void)run{
NSLog(@"NSInvocationOperation");
}
另外NSBlockOperation可以后续继续添加block执行块,操作启动后会在不同线程并发的执行这些执行快:
/*NSBlockOperation初始化*/
NSBlockOperation*blkOperation=[NSBlockOperationblockOperationWithBlock:
^{
NSLog(@"NSBlockOperationA");
}];
[blkOperationaddExecutionBlock:
^{
NSLog(@"NSBlockOperationB");
}];
[blkOperationaddExecutionBlock:
^{
NSLog(@"NSBlockOperationC");
}];
[blkOperationstart];
2017-04-0411:
27:
02.805SingleView[12008:
3666657]NSBlockOperationB
2017-04-0411:
27:
02.805SingleView[12008:
3666742]NSBlockOperationC
2017-04-0411:
27:
02.805SingleView[12008:
3666745]NSBlockOperationA
另外说了NSOperation的可控性比GCD要强,其中一个非常重要的特性是可以设置各操作之间的依赖,即强行规定操作A要在操作B完成之后才能开始执行,成为操作A依赖于操作B:
/*获取主队列(主线程)*/
NSOperationQueue*queue=[NSOperationQueuemainQueue];
/*创建a、b、c操作*/
NSOperation*c=[NSBlockOperationblockOperationWithBlock:
^{
NSLog(@"OperationC");
}];
NSOperation*a=[NSBlockOperationblockOperationWithBlock:
^{
NSLog(@"OperationA");
}];
NSOperation*b=[NSBlockOperationblockOperationWithBlock:
^{
NSLog(@"OperationB");
}];
/*添加操作依赖,c依赖于a和b,这样c一定会在a和b完成后才执行,即顺序为:
A、B、C*/
[caddDependency:
a];
[caddDependency:
b];
/*添加操作a、b、c到操作队列queue(特意将c在a和b之前添加)*/
[queueaddOperation:
c];
[queueaddOperation:
a];
[queueaddOperation:
b];
NSBlockOperation和NSInvocationOperation可以满足多数情况下的编程需求,如果需求特殊则需要继承NSOperation类自定义子类来更加灵活的实现。
常见面试题
问题:
什么是线程?
它与进程有什么区别?
为什么要使用多线程?
线程是指程序在执行过程中,能够执行程序代码的一个执行单元。
线程主要有四种状态:
运行、就绪、挂起、结束。
进程是指一段正在执行的程序。
而线程有时候也被称为轻量级进程,是程序执行的最小单元,一个进程可以拥有多个线程,各个线程之间共享程序的内存空间(代码段、数据段和堆空间)及一些进程级的资源(例如打开的文件),但是各个线程拥有自己的栈空间,进程与线程的关系如下图所示:
在操作系统级别上,程序的执行都是以进程为单位的,而每个进程中通常都会有多个线程互不影响地并发执行,那么为什么要使用多线程呢?
其实,多线程的使用为程序研发带来了巨大的便利,具体而言,有以下几个方面的内容:
使用多线程可以减少程序的响应时间。
在单线程(单线程指的是程序执行过程中只有一个有效操作的序列,不同操作之间都有明确的执行先后顺序)的情况下,如果某个操作很耗时,或者陷入长时间的等待(如等待网络响应),此时程序将不会响应鼠标和键盘等操作,使用多线程后,可以把这个耗时的线程分配到一个单独的线程去执行,使得程序具备了更好的交互性。
与进程相比,线程的创建和切换开销更小。
由于启动一个新的线程必须给这个线程分配独立的地址空间,建立许多数据结构来维护线程代码段、数据段等信息,而运行于同一进程内的线程共享代码段、数据段,线程的启动或切换的开销比进程要少很多。
同时多线程在数据共享方面效率非常高。
多CPU或多核计算机本身就具有执行多线程的能力,如果使用单个线程,将无法重复利用计算机资源,造成资源的巨大浪费。
因此在多CPU计算机上使用多线程能提高CPU的利用率。
使用多线程能简化程序的结构,使程序便于理解和维护。
一个非常复杂的进程可以分成多个线程来执行。
问题:
列举Cocoa中常见的几种多线程的实现,并谈谈多线程安全的几种解决办法,一般什么地方会用到多线程?
问的是三个层次的多线程编程实现;线程锁的使用;
只在主线程刷新访问UI
如果要防止资源抢夺,得用synchronized进行加锁保护
如果异步操作要保证线程安全等问题,尽量使用GCD(有些函数默认就是安全的)
问题:
在iphone上有两件事情要做,请问是在一个线程里按顺序做效率高还是两个线程里做效率高?
为什么?
这里的效率高指的是时间上效率高,也就是希望在最短的时间内完成所有任务,两件事情按顺序做意味着串行执行,第二件事情要等待第一件事情结束后才可开始,效率相对很低。
但如果利用两个线程让两件事情能够并发执行,则时间上效率会大大提高。
问题:
对比在OSX和iOS中实现并发性的不同方法。
在iOS中有三种方法可以实现并发性:
threads、dispatchqueues和operationqueues。
threads的缺点是开发者要自己创建合适的并发解决方案,要决定创建多少线程并且要根据情况动态调整线程的数量。
并且应用还要为创建和维护线程承担主要代价,因此OSX和IOS系统并不是依靠线程来解决并发问题的,而是采用了异步设计的途径。
其中一个开启异步任务的技术是GCD(GrandCentralDispatch),让系统层次来对线程进行管理。
开发者要做的就是定义要执行的任务并将之添加到合适的dispatch分派队列。
GCD会负责创建需要的线程并安排任务在这些线程上运行。
所有的dispatch队列都是先进先出(FIFO)队列结构,所以任务的执行顺序是和添加顺序是一致的。
operationqueues和并发dispatch队列一样都是通过Cocoa框架的NSOperationQueue类实现的,不过它并不一定是按照先进先出的顺序执行任务的,而是支持创建更复杂的执行顺序图来管理任务的执行顺序。
问题:
用户下载一个图片,图片很大,需要分成很多份进行下载,使用GCD应该如何实现?
使用什么队列?
使用DispatchGroup追加block到GlobalGroupQueue,这些block如果全部执行完毕,就会执行MainDispatchQueue中的结束处理的block。
dispatch_queue_tqueue=dispatch_get_global_queue(DI
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- iOS 沉思 NSThreadGCDNSOperation 多线程 编程 总结