论文部分内容阅读
摘要:软件在设计开发过程中,都会考虑同时执行多个任务的能力,需要通过多线程的技术来实现。同样在lOS系统中,应用软件也需要具有并发执行多个任务的能力。文章内容从多线程的概率出发,主要介绍了iOS系统中使用最广泛的三种多线程技术,即NSThread、GCD、NSOperation。并分别对三种技术的使用做了进一步的研究与说明,介绍了各个多线程技术在使用过程中的一些优缺点,以及开发人员在实际应用过程中需要注意的地方。对从事iOS软件编程方面对多线程技术存在困惑的人具有一定的帮助和指导作用。
关键词:iOS系统;多线程;NSThread;GCD;NSOperation
中图分类号:TP311.1 文献标识码:A 文章编号:1009-3044(2017)08-0078-03
1背景
目前,阻礙计算机性能充分发挥的主要因素是中心处理器的运行速度。在计算机技术快速发展的前提下,单核处理器已经完全满足不了我们日常的使用需求。在这种背景下,一些芯片制造商纷纷开始转向多核处理器的设计,使计算机拥有同时执行多个任务的能力。美国苹果公司的iPhone手机近几年如此之火爆,不仅是因为它漂亮的外表和丰富的软件界面,更是因为它充分利用了多核处理器的核心优势。在任何时候都可以执行系统相关的任务,而且应用程序也可以通过使用多线程技术流畅的执行多个任务,使得iPhone具有卓越的性能和优秀的用户体验。通过使用多线程技术,可以让比较耗时的操作放在子线程中执行,保证主线程只需要执行和用户界面相关的操作,不会有一种界面很“卡”的感觉,确保软件在使用过程中流畅性较好,增强了用户体验性能。
2多线程的介绍
2.1进程和线程
简单点概况,进程就是计算机系统中正在运行的程序,是CPU分配资源的基本单位。进程与线程之间是相互独立的概念,每一个进程只在受保护的专属内存空间内运行,而且同一个进程内的线程共享进程中的数据资源。比如我们在电脑上同时打开浏览器与音乐播放器,系统就会自动开启两个进程。
线程是CPU建立在进程基础之上执行任务的最小单位,且每个进程至少有一条主线程,进程中的所有任务都是在线程中执行的。比如用浏览器浏览网页,播放器播放音乐,都需要在每个进程中的线程去执行。每个线程中执行任务的方式是串行的,即在同一时间只可能执行一个任务。执行多个任务时也只能按照任务的先后顺序一个一个的执行。在Mac和iOS系统中运行的应用程序每多创建一个新的线程都需要占用512KB(主线程占用1MB)的内存和消耗一定的CPU时间。
2.2多线程
多线程是指让应用程序的内部多个线程并发执行任务的一种能提高执行效率与资源利用率的技术。同一时间段内CPU只能处理一条线程上的任务,而多线程并发执行的实质是CPU快速地在多条线程之间的相互切换,如果线程调度的时间足够快,就造成一种多线程并发执行的假象用。如果一个应用程序线程开的太多会消耗大量的CPU资源,降低程序的性能,每条线程的执行频率和效率也会大大地降低。
每个lOS应用程序运行后,默认开启一条主线程(或称之为UI线程),其作用是显示和刷新UI(User Interface/用户界面),以及处理UI事件(比如点击事件、滚动事件、拖拽事件等)。
3iOS系统中三种多线程技术
iOS系统中多线程技术的实现方案总共有四种,分别为Pthread、NSThread、GCD(Grand Central Dispatch)、NSOperation技术。Pthread技术由于其使用难度大等一系列的缺陷,目前在实际的APP开发过程中基本上不使用这种淘汰的技术。因此主要以介绍NSThread、GCD、NSOperation技术的研究为主。
3.1 NSThread多线程技术
NSThread是OS X和iOS系统中都提供的一个以轻量级实现的线程对象。NSThread适应于一些轻量级较简单的任务,不过用户需要自己管理线程生命周期以及线程之间的同步。NSThread没有涉及到线程状态、依赖性、线程间的同步问题,当多个线程访问同一份资源时会出现数据错乱的现象,这时我们又要使用互斥锁来解决这个问题,而这又会消耗大量的CPU资源。
NSThread的初始化方法
3)动态方法
动态方法在初始化后,需要手动的调用strut方法才能开启该线程的真正创建。
其中,target:selector消息发送的目标对象;selector:线程选择调用的方法,最多只能接收一个参数;argument:传给selector的唯一参数,也可以是nil。
4)静态方法
5)隐式创建线程
静态方法和隐式方法在代码执行后会默认开启一条线程执行任务。
3.2GCD多线程技术
GCD(Grand Center Dispatch)的实现是基于c语言的API。GCD是一个大的主题,负责自动创建管理线程的生命周期和调度执行任务,提高代码的执行效率与多核的利用率。GCD的API很大程度上基于block,当配合block使用时,GCD能发挥其最大能力。通过使用GCD技术管理线程,工程师可以不需要再编写线程代码,只需要专注于在block或方法中编写执行某项功能的代码。队列这个概念为GCD的实现提供了方便,其实质是将长期运行的任务拆分成多个工作单元,并将这些单元添加到dispath queue(调度队列)中,系统会自动管理这些dispathqueue的生命周期以及任务的执行,不需要我们手动管理后台线程。而dispath queue也严格遵循FIFO(先进先出)原则,串行或并发地执行任务。
1)获得全局并发dispatch queue 并发dispateh queue可以同时并行的按照queue加入的先后顺序执行多个任务。系统通常会默认的提供三个全局享用的并发dispateh queue给每个应用程序,三个queue的区别是优先级的不同。这些queue不用显式地创建,只需要通过dis-patch_get_global_queue函数就可以获取到这三个queue。
图4中第一个参数用于指定线程的优先级,分别使用DIS-PATCH_QUEUE_PRIORITY_HIGH和D/SPATCH_QUEUE_PRI-ORITY_LOW两个常量来获取高和低優先级的两个queue;第二个参数默认0即可。
2)创建串行dispatch queue
创建方法:dispateh_queue_create(const char*_Nullable la-bel,dispatch_queue_attr_t_Nullable attr);
函数的第一个参数是一个标签,苹果公司建议我们使用倒置域名来命名队列,比如“com.apple.www”。第二个参数是队列的类型,只能传参数DISPATCH_QUEUE_SERIAL(串行队列)和NULL,不能传DISPATCH_QUEUE_CONCURRENT(并发队列)。串行队列一次只能执行一个任务,只有当前任务已经执行完成才会启动下一个任务的执行;并发队列则会尽可能多地启动任务并发执行,并且只能在异步函数下有效。
3)添加任务到queue
异步函数:dispatch_async(dispatch_queue_t queue,dis-patch_block_t blockk
同步函数:dispatch_sync(dispatch_queue_t queue,DIS-PA TCH_NOESCAPE dispateh_block_t block);
其中第一个参数表示已经创建的队列名称,第二个参数是需要实现某些具体功能的代码段(即block任务)。异步函数具备开启新线程执行任务的能力,在调用时会立即返回,同时队列会在轮到这个block执行时后台异步执行这个任务,这样就可以调用线程继续去做其它事情。而同步函数不具备开新线程的能力,在调用时会在当前线程等待block中的代码执行完成后返回,容易出现阻塞当前调用线程的现象,而在串行queue中会导致死锁。
在传统的多线程编程中,你可能有一个对象要被多个线程使用,这时候你需要一个互斥锁来保护这个对象。然而在GCD中,用户队列完全可以替代互斥锁来完成同步机制,就不可能意外写出具有不成对Lock(锁)的代码。
3.3 NSOperation和NSOperationQueue
配合使用NSOperation和NSOperationQueue也能实现多线程的功能。NSOperation实例中已经封装好了开发过程中需要执行的操作以及所需的数据,能够以并发或非并发的方式执行这个操作。NSOperation是一个抽象的基类,本身不具备把操作进行封装的能力,必须使用它的子类才能实现。目前有三种可以使用NSOperation子类的方法,Foundation框架为我们提供了两个具体子类直接供我们使用,即NShwocationOperation和NSBlockOperation方法;另外一个是自定义子类继承自NSOper-ation,实现内部相应的方法实现。
1)创建NSInvocationOperation对象
创建方法:-(nullable instancetype)init WithTarget:(id)targetselector:(SEL)sel object:(nullable id)arg;
其中,三个参数分别表示目标对象、调用方法以及方法需要传递的参数。在使用时需要调用start方法开始执行操作。默认情况下,调用start方法后并不会开一条新线程去执行操作,而是在当前线程同步执行操作,直到任务完成。如果想要中途取消操作,可以调用cancel方法。
上面是调用self均test方法,通过调用start方法开始执行任务。
2)创建NSBlockOperation对象
创建方法: (instancetype)blockOperationWithBlock:(void (^)(void))block;
这种方法能够并发地执行一个或多个block对象,所有相关的block都执行完之后,操作才算完成。其次,通过调用ad-dExecutionBlock:方法可以添加更多的block操作。
图7中首先创建了一个名称为operation的NSBlockOpera-tion对象,通过addExecationBlock方法添加三个新的block操作,即NSBlockOperation封装的操作数大于1,这样可以使4个block操作并发的在不同线程中执行。
NSOperation虽然可以调用start方法来执行任务,但默认是同步执行的,如果将NSOperation添加到NSOperationQueue(操作队列)中,系统会自动异步执行NSOperation中的操作。
对于上述NSlnvocationOperation和NSBlockOperation方法,我们需要创建一个NSOperationQueue对象,将封装好操作代码的NSOperation对象添加到NSOperationQueue中,系统会自动将NSOperationQueue中的NSOperation对象提取出来,然后将NSOperation封装的操作放到一条新线程中执行。
3)自定义NSOperation对象 如果NShwocationOperation和NSBlockOperation对象不能满足我们的任务需求,可以自定义一个直接继承自NSOperation的对象,并在其中添加任何想要的方法。
图8中的CHWOperation是我們自定义的继承自NSOpera-tion的对象,在定义非并发的NSOperation对象只需要重载-(Void)main方法,在这个方法里面添加执行主任务的代码,并正确地响应任务取消事件;对于并发NSOperation,需要重写NSOperation的多个基本方法进行实现。
4)其他相关用法
有时,仅仅依靠这些方法并不能满足我们的需求,所以我们需要通过其他方法使任务更加合理有序的执行。通过-(NSInte-ger)maxConcurrentOperationCount方法,可以设置同时执行任务的最大并发数。当队列有取消、暂停、恢复需求时,调用-(void)cancelAllOperatious方法取消所有队列正在执行的任务,-(void)cancel方法取消单个队列操作,而NSOperation对象会定期地调用isCancelled方法检测操作是否已经被取消,这种方法本身非常轻量即使是频繁地调用也不会产生比较大的性能损失;设置Suspended方法的BOOL值,YES表示暂停,NO表示取消。
NSOperation之间还可以设置依赖来保证任务的执行顺序,比如要让操作B在操作A完成之后再执行,可以设置[opera-tionB addDependency:operationA],即操作B依赖于操作A。同样在不同queue之间的NSOperation也可以创建依赖关系。不过值得我们注意的是任何两个操作之间不能相互依赖。
4结束语
根据在iOS开发学习过程中出现的问难,本文详细讨论了iOS系统中三种多线程技术的使用,对于在iOS编程中对多线程有疑问的开发者具有一定的帮助作用。而通过对三种多线程技术的研究比较,在实际的开发过程中,开发者应该尽可能的少用需要人工管理生命周期的NSThread技术,转而使用具有自动管理生命周期功能的GCD和NSOperation多线程技术。此外,iOS系统目前已经更新到了iOS10,以后技术还会不断提高,目前的多线程技术也肯定满足不了以后的软件需求。所以,开发人员需要不断提高自身的技术能力,结合Apple公司提供的方法,研究出更加完善的多线程技术,以此来推动lOS系统的不断发展。
关键词:iOS系统;多线程;NSThread;GCD;NSOperation
中图分类号:TP311.1 文献标识码:A 文章编号:1009-3044(2017)08-0078-03
1背景
目前,阻礙计算机性能充分发挥的主要因素是中心处理器的运行速度。在计算机技术快速发展的前提下,单核处理器已经完全满足不了我们日常的使用需求。在这种背景下,一些芯片制造商纷纷开始转向多核处理器的设计,使计算机拥有同时执行多个任务的能力。美国苹果公司的iPhone手机近几年如此之火爆,不仅是因为它漂亮的外表和丰富的软件界面,更是因为它充分利用了多核处理器的核心优势。在任何时候都可以执行系统相关的任务,而且应用程序也可以通过使用多线程技术流畅的执行多个任务,使得iPhone具有卓越的性能和优秀的用户体验。通过使用多线程技术,可以让比较耗时的操作放在子线程中执行,保证主线程只需要执行和用户界面相关的操作,不会有一种界面很“卡”的感觉,确保软件在使用过程中流畅性较好,增强了用户体验性能。
2多线程的介绍
2.1进程和线程
简单点概况,进程就是计算机系统中正在运行的程序,是CPU分配资源的基本单位。进程与线程之间是相互独立的概念,每一个进程只在受保护的专属内存空间内运行,而且同一个进程内的线程共享进程中的数据资源。比如我们在电脑上同时打开浏览器与音乐播放器,系统就会自动开启两个进程。
线程是CPU建立在进程基础之上执行任务的最小单位,且每个进程至少有一条主线程,进程中的所有任务都是在线程中执行的。比如用浏览器浏览网页,播放器播放音乐,都需要在每个进程中的线程去执行。每个线程中执行任务的方式是串行的,即在同一时间只可能执行一个任务。执行多个任务时也只能按照任务的先后顺序一个一个的执行。在Mac和iOS系统中运行的应用程序每多创建一个新的线程都需要占用512KB(主线程占用1MB)的内存和消耗一定的CPU时间。
2.2多线程
多线程是指让应用程序的内部多个线程并发执行任务的一种能提高执行效率与资源利用率的技术。同一时间段内CPU只能处理一条线程上的任务,而多线程并发执行的实质是CPU快速地在多条线程之间的相互切换,如果线程调度的时间足够快,就造成一种多线程并发执行的假象用。如果一个应用程序线程开的太多会消耗大量的CPU资源,降低程序的性能,每条线程的执行频率和效率也会大大地降低。
每个lOS应用程序运行后,默认开启一条主线程(或称之为UI线程),其作用是显示和刷新UI(User Interface/用户界面),以及处理UI事件(比如点击事件、滚动事件、拖拽事件等)。
3iOS系统中三种多线程技术
iOS系统中多线程技术的实现方案总共有四种,分别为Pthread、NSThread、GCD(Grand Central Dispatch)、NSOperation技术。Pthread技术由于其使用难度大等一系列的缺陷,目前在实际的APP开发过程中基本上不使用这种淘汰的技术。因此主要以介绍NSThread、GCD、NSOperation技术的研究为主。
3.1 NSThread多线程技术
NSThread是OS X和iOS系统中都提供的一个以轻量级实现的线程对象。NSThread适应于一些轻量级较简单的任务,不过用户需要自己管理线程生命周期以及线程之间的同步。NSThread没有涉及到线程状态、依赖性、线程间的同步问题,当多个线程访问同一份资源时会出现数据错乱的现象,这时我们又要使用互斥锁来解决这个问题,而这又会消耗大量的CPU资源。
NSThread的初始化方法
3)动态方法
动态方法在初始化后,需要手动的调用strut方法才能开启该线程的真正创建。
其中,target:selector消息发送的目标对象;selector:线程选择调用的方法,最多只能接收一个参数;argument:传给selector的唯一参数,也可以是nil。
4)静态方法
5)隐式创建线程
静态方法和隐式方法在代码执行后会默认开启一条线程执行任务。
3.2GCD多线程技术
GCD(Grand Center Dispatch)的实现是基于c语言的API。GCD是一个大的主题,负责自动创建管理线程的生命周期和调度执行任务,提高代码的执行效率与多核的利用率。GCD的API很大程度上基于block,当配合block使用时,GCD能发挥其最大能力。通过使用GCD技术管理线程,工程师可以不需要再编写线程代码,只需要专注于在block或方法中编写执行某项功能的代码。队列这个概念为GCD的实现提供了方便,其实质是将长期运行的任务拆分成多个工作单元,并将这些单元添加到dispath queue(调度队列)中,系统会自动管理这些dispathqueue的生命周期以及任务的执行,不需要我们手动管理后台线程。而dispath queue也严格遵循FIFO(先进先出)原则,串行或并发地执行任务。
1)获得全局并发dispatch queue 并发dispateh queue可以同时并行的按照queue加入的先后顺序执行多个任务。系统通常会默认的提供三个全局享用的并发dispateh queue给每个应用程序,三个queue的区别是优先级的不同。这些queue不用显式地创建,只需要通过dis-patch_get_global_queue函数就可以获取到这三个queue。
图4中第一个参数用于指定线程的优先级,分别使用DIS-PATCH_QUEUE_PRIORITY_HIGH和D/SPATCH_QUEUE_PRI-ORITY_LOW两个常量来获取高和低優先级的两个queue;第二个参数默认0即可。
2)创建串行dispatch queue
创建方法:dispateh_queue_create(const char*_Nullable la-bel,dispatch_queue_attr_t_Nullable attr);
函数的第一个参数是一个标签,苹果公司建议我们使用倒置域名来命名队列,比如“com.apple.www”。第二个参数是队列的类型,只能传参数DISPATCH_QUEUE_SERIAL(串行队列)和NULL,不能传DISPATCH_QUEUE_CONCURRENT(并发队列)。串行队列一次只能执行一个任务,只有当前任务已经执行完成才会启动下一个任务的执行;并发队列则会尽可能多地启动任务并发执行,并且只能在异步函数下有效。
3)添加任务到queue
异步函数:dispatch_async(dispatch_queue_t queue,dis-patch_block_t blockk
同步函数:dispatch_sync(dispatch_queue_t queue,DIS-PA TCH_NOESCAPE dispateh_block_t block);
其中第一个参数表示已经创建的队列名称,第二个参数是需要实现某些具体功能的代码段(即block任务)。异步函数具备开启新线程执行任务的能力,在调用时会立即返回,同时队列会在轮到这个block执行时后台异步执行这个任务,这样就可以调用线程继续去做其它事情。而同步函数不具备开新线程的能力,在调用时会在当前线程等待block中的代码执行完成后返回,容易出现阻塞当前调用线程的现象,而在串行queue中会导致死锁。
在传统的多线程编程中,你可能有一个对象要被多个线程使用,这时候你需要一个互斥锁来保护这个对象。然而在GCD中,用户队列完全可以替代互斥锁来完成同步机制,就不可能意外写出具有不成对Lock(锁)的代码。
3.3 NSOperation和NSOperationQueue
配合使用NSOperation和NSOperationQueue也能实现多线程的功能。NSOperation实例中已经封装好了开发过程中需要执行的操作以及所需的数据,能够以并发或非并发的方式执行这个操作。NSOperation是一个抽象的基类,本身不具备把操作进行封装的能力,必须使用它的子类才能实现。目前有三种可以使用NSOperation子类的方法,Foundation框架为我们提供了两个具体子类直接供我们使用,即NShwocationOperation和NSBlockOperation方法;另外一个是自定义子类继承自NSOper-ation,实现内部相应的方法实现。
1)创建NSInvocationOperation对象
创建方法:-(nullable instancetype)init WithTarget:(id)targetselector:(SEL)sel object:(nullable id)arg;
其中,三个参数分别表示目标对象、调用方法以及方法需要传递的参数。在使用时需要调用start方法开始执行操作。默认情况下,调用start方法后并不会开一条新线程去执行操作,而是在当前线程同步执行操作,直到任务完成。如果想要中途取消操作,可以调用cancel方法。
上面是调用self均test方法,通过调用start方法开始执行任务。
2)创建NSBlockOperation对象
创建方法: (instancetype)blockOperationWithBlock:(void (^)(void))block;
这种方法能够并发地执行一个或多个block对象,所有相关的block都执行完之后,操作才算完成。其次,通过调用ad-dExecutionBlock:方法可以添加更多的block操作。
图7中首先创建了一个名称为operation的NSBlockOpera-tion对象,通过addExecationBlock方法添加三个新的block操作,即NSBlockOperation封装的操作数大于1,这样可以使4个block操作并发的在不同线程中执行。
NSOperation虽然可以调用start方法来执行任务,但默认是同步执行的,如果将NSOperation添加到NSOperationQueue(操作队列)中,系统会自动异步执行NSOperation中的操作。
对于上述NSlnvocationOperation和NSBlockOperation方法,我们需要创建一个NSOperationQueue对象,将封装好操作代码的NSOperation对象添加到NSOperationQueue中,系统会自动将NSOperationQueue中的NSOperation对象提取出来,然后将NSOperation封装的操作放到一条新线程中执行。
3)自定义NSOperation对象 如果NShwocationOperation和NSBlockOperation对象不能满足我们的任务需求,可以自定义一个直接继承自NSOperation的对象,并在其中添加任何想要的方法。
图8中的CHWOperation是我們自定义的继承自NSOpera-tion的对象,在定义非并发的NSOperation对象只需要重载-(Void)main方法,在这个方法里面添加执行主任务的代码,并正确地响应任务取消事件;对于并发NSOperation,需要重写NSOperation的多个基本方法进行实现。
4)其他相关用法
有时,仅仅依靠这些方法并不能满足我们的需求,所以我们需要通过其他方法使任务更加合理有序的执行。通过-(NSInte-ger)maxConcurrentOperationCount方法,可以设置同时执行任务的最大并发数。当队列有取消、暂停、恢复需求时,调用-(void)cancelAllOperatious方法取消所有队列正在执行的任务,-(void)cancel方法取消单个队列操作,而NSOperation对象会定期地调用isCancelled方法检测操作是否已经被取消,这种方法本身非常轻量即使是频繁地调用也不会产生比较大的性能损失;设置Suspended方法的BOOL值,YES表示暂停,NO表示取消。
NSOperation之间还可以设置依赖来保证任务的执行顺序,比如要让操作B在操作A完成之后再执行,可以设置[opera-tionB addDependency:operationA],即操作B依赖于操作A。同样在不同queue之间的NSOperation也可以创建依赖关系。不过值得我们注意的是任何两个操作之间不能相互依赖。
4结束语
根据在iOS开发学习过程中出现的问难,本文详细讨论了iOS系统中三种多线程技术的使用,对于在iOS编程中对多线程有疑问的开发者具有一定的帮助作用。而通过对三种多线程技术的研究比较,在实际的开发过程中,开发者应该尽可能的少用需要人工管理生命周期的NSThread技术,转而使用具有自动管理生命周期功能的GCD和NSOperation多线程技术。此外,iOS系统目前已经更新到了iOS10,以后技术还会不断提高,目前的多线程技术也肯定满足不了以后的软件需求。所以,开发人员需要不断提高自身的技术能力,结合Apple公司提供的方法,研究出更加完善的多线程技术,以此来推动lOS系统的不断发展。