iOS并发程序设计中几种方法的特点及使用技巧研究

2016-05-18 14:21李岚
电脑知识与技术 2016年9期
关键词:多线程

李岚

摘要:如今移动设备已经进入了多核心CPU 时代,并且随着时间的推移,CPU 的核心数只会增加不会减少。作为软件开发者需要尽可能地提高应用的并发性,充分利用多核心CPU 的性能。iOS是由苹果公司开发的移动操作系统,在 iOS 开发中,主要通过 Dispatch Queues 、Dispatch Sources和Operation Queues 来提高应用的并发性。该文对针对这三种工具的使用方法及技巧进行了研究,总结出提高多线程编程的效率的方法。

关键词:iOS ;并发程序;多线程

中图分类号:TP311 文献标识码:A 文章编号:1009-3044(2016)09-0083-02

iOS是苹果公司开发的移动操作系统,随着苹果电子产品的风靡世界,iOS版本的不断更新,“果粉”们对新应用的需求量日益增加,越来越多的程序员选择加入到iOS应用开发的行列中。 在如今多核心CPU时代,如何提高所开发应用的并发性,充分利用多核心CPU,是软件开发者需要思考的问题。

Grand Central Dispatch (GCD)是苹果公司开发的一个多核编程的解决方法。该方法的需要在iOS4.0以上的版本中使用。

Dispatch Queues、Dispatch Source 和Operation Queue是iOS中的多线程编程中通常使用的三种方法,开发过程通过使用它们可以提高应用的并发性。因为它们都能“自动地”解决在什么时间、何种系统可用资源条件下建立及维护线程的支持工作。同时,向程序员隐藏了线程这个名称,并以工作队列这样抽象的对象,使得并发程序更加方便实现。其中Dispatch Queues和Dispatch Source两种方法是基于GCD的。下面依次介绍三种方法的使用特点及技巧。

1 Dispatch Queue的使用

Dispatch Queue作为对象,接受任务,将任务以先进先出的顺序执行。并且它还有着编程简单、能够提供线程池的管理、节约内存空间的使用、不trap内核、无死锁、在顺序的队列中比直接加锁同步的执行速度快等一系列的优点。

Dispatch Queue可以并发执行任务,也能够串行执行任务。并发任务只要是在队列中的工作,都会被陆续地扔在各个Thread中去执行,它们之间相互独立,不需要等待其中一个结束了,再由队列扔出一个新的任务到Thread上去执行,而且是同时在各个Thread中各自执行。串行任务则意味着在这个队列中的各种操作,需要“一个一个”地按顺序进行执行,下一个任务需要等待上一个任务执行结束后,才会进入执行。

1.1 Dispatch Queue中队列的三种分类

Dispatch Queue中队列的队列分为顺序队列、并发队列和Main dispatch queue三种。

所谓顺序队列即是在一个queue中,每次仅1个任务在执行。

并发队列即是在一个queue中,每次多于1个任务在执行,而这个多的数量由queue自己决定,它是根据相应的系统资源来决定的,资源多则数量多,资源少则数量少。

main dispatch queue,它是一个顺序的队列,它在main thread上执行,程序员也可以称main thread为”程序同步关键点“。

1.2 某些关键的“优点”

1)如果通过线程编程,其中2个线程上的task,都去访问共享资源。通过使用lock来进行线程的同步,额外的会造成相应的内核中断。而相应的,对于Dispatch Queue而言,可以使用顺序的queue来进行处理,从而提高效率。

2)多个Dispatch Queue的之间是并发的执行的,若是顺序的话,单个Dispatch Queue之内是顺序的。

3)Dispatch Queue会根据系统状况来选择create thread的数目,不会无限制扩张。

1.3 block的问题

1)block看上去像函数指针,其实它是一个基本的数据结构对象,由编译器建立和管理。

2)block可以访问其父作用域上的变量,如果不加__block,则是把这个变量拷贝到block自身的堆内存空间上。

3)block不要去使用父作用域上的指针变量,这些指针指向的对象的生命周期是由caller来决定的。也就是说,block不要引用一个野指针。

4)queue会copy一个block到其中,当block执行完毕,它会做release的处理。

1.4 queue 需要release的问题

全局的queue,不需要retain和release。自己create的queue,需要release。

1.5 queue的自身的context的设定

每一个queue都可以设定用户自定义的context数据,可以通过调用dispatch_set_context(queue,contextdata)设置context数据,通过dispatch_set_finalizer_f来设定怎样释放其对应queue的contextdata的函数指针。

1.6 dispatch group

dispatch group是一个lock的方法,可以lock一个或一系列task完成的条件,使得后续代码要等待某task执行完毕后,再继续执行。

1.7 线程安全

1)对于Dispatch Queue自身来说,线程是安全的。

2)不要在同一个queue中,调用dispatch_sync函数,如果这样调用,将会出现死锁的现象。例如:在a中同步调用b,结果b的block一直等待a的资源,而b不执行完毕,a是没办法继续向下执行

2 Dispatch Source的使用

Dispatch Source是GCD的一部分,是用于事件响应的队列。用于监听系统push上来的timer、 signal以及各种event,一旦发生,立即把关联的block对象扔到Dispatch Queue上执行。

2.1 创建Dispatch Source

需要创建事件源和Dispatch Source本身。事件源是处理事件所需要的native数据结构,例如基于描述符的Dispatch Source,你需要打开描述符;基于进程的事件,你需要获得目标程序的进程ID。

然后可以如下创建相应的Dispatch Source:

使用dispatch_source_create 函数创建Dispatch Source

配置Dispatch Source:

为Dispatch Source设置一个事件处理器

对于定时器源,使用 dispatch_source_set_timer 函数设置定时器信息

为Dispatch Source赋予一个取消处理器(可选)调用 dispatch_resume 函数开始处理事件由于Dispatch Source必须进行额外的配置才能被使用,dispatch_source_create 函数返回的Dispatch Source将处于挂起状态。此时Dispatch Source会接收事件,但是不会进行处理。这时候你可以安装事件处理器,并执行额外的配置。

2.2 编写和安装一个事件处理器

需要定义一个事件处理器来处理事件,可以是函数或block对象,并使用 dispatch_source_set_event_handler 或 dispatch_source_set_event_handler_f 安装事件处理器。事件到达时,Dispatch Source会提交你的事件处理器到指定的Dispatch Queue,由queue执行事件处理器。

事件处理器的代码负责处理所有到达的事件。如果事件处理器已经在queue中并等待处理已经到达的事件,如果此时又来了一个新事件,Dispatch Source会合并这两个事件。事件处理器通常只能看到最新事件的信息,不过某些类型的Dispatch Source也能获得已经发生以及合并的事件信息。

如果事件处理器已经开始执行,一个或多个新事件到达,Dispatch Source会保留这些事件,直到前面的事件处理器完成执行。然后以新事件再次提交处理器到queue。

函数事件处理器有一个context指针指向Dispatch Source对象,没有返回值。Block事件处理器没有参数,也没有返回值。

3 Operation Queue的使用

Operation Queue的实现机制,是使用NSOperationQueue和NSOperation来完成的。

通常,Operation Queue中的执行对象是NSOperation类的子类的对象(NSOperation类是抽象的)。

相对于Dispatch Queue的FIFO而言,这里的顺序,依赖于NSOperation之间的依赖性来选择执行顺序的。因而它是按照一定顺序来执行的,并且是并发抛出的。

4 结论

4.1 使用并发、多线程几种情况

1)把任务进行分解,如果子任务之间的行为是相互独立的,那么可以确认在子任务之间可以是多线程执行的。

2)如果子任务之间相互存在依赖关系。例如:下一个子任务的输出必须通过上一个子任务的输入来获取,则最好不用并发异步。

3)独立的子任务确认后,就根据顺序还是并发的需求,塞入相应的group就好。

4)对于operation object而言,如果要设计成顺序执行的情况,就必须要设置任务之间的依赖,如果用block,就在Dispatch Queue中设置顺序即可,其他默认并发。

4.2 提高多线程编程效率的技巧:

1)尽量不要让共享资源lock住线程的并发。尽量使用线程的局部value来代替共享资源。变成可重入。

2)禁止使用lock。如果并发需要lock来同步,那么尽量使用serial queue来进行队列的方式来设计软件架构。但是注意,如果iOS的一些回调函数,或者通知函数,打回来时就是开启新线程,那么就要考虑对new Thread进行加入lock了。但是这是自己没法控制的时候,这样做的,如果代码部分是可以自己控制的,则尽量禁止使用lock机制,或者信号量。如果同时建立1k个operation对象,然后提交到operation queue中,那么会引起不断的内存换页。

参考文献:

[1] Rob Napier,Mugunth Kumar .iOS编程实战[M]. 美团移动,译.北京: 人民邮电出版社,2014.

[2] 关东升. iOS开发指南:从零基础到App Store上架[M].2版. 北京:人民邮电出版社,2014.

[3] Kazuki Sakamoto, Tomohiko Furumoto . Objective-C高级编程 iOS与OS X多线程和内存管理[M].黎华,译. 北京:人民邮电出版社,2013.

[4] Erica Sadun ,iOS Auto Layout Demystified ,Second Edition[M]. 2nd ed,Addison-Wesley Educational Publishers Inc, 2013.

猜你喜欢
多线程
Java多线程同步机制在网络售票系统中的应用
Java并发工具包对并发编程的优化