iOS多线程调研

关于多线程的基础释义就很少作解释了,下面引用一句百度百科上的话做为开头:ios

多线程是为了同步完成多项任务,不是为了提升运行效率,而是为了提升资源使用效率来提升系统的效率。线程是在同一时间须要完成多项任务的时候实现的。程序员

本文涉及内容数据库

  • 多线程的目的
  • 多线程的优缺点
  • 为何要用多线程
  • 何时使用多线程
  • iOS线程状态
  • iOS线程安全
  • iOS常见多线程
    • Pthread
    • NSThread
    • NSOperation
    • Grand Central Dispatch(GCD)

一. 目的

随着计算机技术的发展,编程模型也愈来愈复杂多样化。但多线程编程模型是目前计算机系统架构的最终模型。随着CPU主频的不断攀升,X86架构的硬件已经成为瓶,在这种架构的CPU主频最高为4G。事实上目前3.6G主频的CPU已经接近了顶峰。   若是不能从根本上更新当前CPU的架构(在很长一段时间内还不太可能),那么继续提升CPU性能的方法就是超线程CPU模式。那么,做业系统、应用程序要发挥CPU的最大性能,就是要改变到以多线程编程模型为主的并行处理系统和并发式应用程序。   因此,掌握多线程编程模型,不只是目前提升应用性能的手段,更是下一代编程模型的核心思想。多线程编程的目的,就是"最大限度地利用CPU资源",当某一线程的处理不须要占用CPU而只资源打交道时,让须要占用CPU资源的其它线程有机会得到CPU资源。从根本上说,这就是多线程编程的最终目的。   也就是说多线程的目的不是为了提升运行效率,而是为了提升资源使用效率,为了最大限度地利用CPU资源。   其实仔细想一想,若是这就是正确答案的话就不可避免的产生一个矛盾。假设一下,有一个任务须要分红不等的小任务执行,若是按照上述结果看,最好是分红尽量多的线程并发处理,这样能够最大限度的利用CPU的资源,而且能够最短期内完成任务。可是某些状况会出现不同的状况,好比说这个任务是数据库的增删改查,为了保证数据的正确性咱们须要进行加锁同步等处理。任务执行时CPU还须要不断的切换子线程进行任务处理,这样看不光没有提升运行效率,反而拉低了运行效率,并且大量占用了CPU资源,浪费不少的内存空间(默认状况下,主线程占用1M,子线程占用512KB),那么到底该不应使用多线程?编程

二. 优缺点

从上述问题来看,到底该不应使用多线程,为何要用多线程就是个问题了。为了想清楚这个问题,接下来先了解一下多线程的优缺点:缓存

1. 多线程的优势

  • 使用线程能够把占据时间长的程序中的任务放到后台去处理
  • 用户界面能够更加吸引人,这样好比用户点击了一个按钮去触发某些事件的处理,能够弹出一个进度条来显示处理的进度
  • 程序的运行速度可能加快
  • 在一些等待的任务实现上如用户输入、文件读写和网络收发数据等,线程就比较有用了。在这种状况下能够释放一些珍贵的资源如内存占用等等。
  • 多线程技术在IOS软件开发中也有举足轻重的位置。
  • 线程应用的好处还有不少,就不一一说明了   -- 引自百度百科

2. 多线程的缺点

  • 若是有大量的线程,会影响性能,由于操做系统须要在它们之间切换。
  • 更多的线程须要更多的内存空间。
  • 线程可能会给程序带来更多“bug”,所以要当心使用。
  • 线程的停止须要考虑其对程序运行的影响。
  • 一般块模型数据是在多个线程间共享的,须要防止线程死锁状况的发生。   -- 引自百度百科

三. 为何要用多线程

为何要使用多线程,维基百科上有这样一段话:安全

一个线程持续运行,直到该线程被一个事件挡住而制造出长时间的延迟(多是内存load/store操做,或者程序分支操做)。该延迟一般是因缓存失败而从核心外的内存读写,而这动做会使用到几百个CPU周期才能将数据回传。与其要等待延迟的时间,线程化处理器会切换运行到另外一个已就绪的线程。只要当以前线程中的数据送达后,上一个线程就会变成已就绪的线程。这种方法来自各个线程的指令交替执行,能够有效的掩盖内存访问时延,填补流水线空洞。 举例来讲: 周期 i :接收线程 A 的指令 j 周期 i+1:接收线程 A 的指令 j+1 周期 i+2:接收线程 A 的指令 j+2,而这指令缓存失败 周期 i+3:线程调度器介入,切换到线程 B 周期 i+4:接收线程 B 的指令 k 周期 i+5:接收线程 B 的指令 k+1 在概念上,它与即时操做系统中使用的合做式多任务相似,在该任务须要为一个事件等待一段时间的时候会主动放弃运行时段。 -- 维基百科bash

假设一下,若是你的程序是单线程,而后网络比较慢的状况下下载了一张图片,个人天,用户能够洗洗睡了。 再假设一下,如今有三个任务须要处理。单独一条线程处理它们分别须要五、三、3秒。若是三个CPU并行处理,那么一共只须要5秒。相比于串行处理,节约了6秒。而同步/异步,描述的是任务之间前后顺序问题。假设须要5秒的那个是保存数据的任务,而另外两个是UI相关的任务。那么经过异步执行第一个任务,咱们省去了5秒钟的卡顿时间。 对于同步执行的三个任务来讲,系统倾向于在同一个线程里执行它们。由于即便开了三个线程,也得等他们分别在各自的线程中完成。并不能减小总的处理时间,反而徒增了线程切换(这就是文章开头举的例子) 对于异步执行的三个任务来讲,系统倾向于在三个新的线程里执行他们。由于这样能够最大程度的利用CPU性能,提高程序运行效率。 综合上述而言咱们能够得出结论,在须要同时处理IO和UI的状况下,真正起做用的是异步,而不是多线程。能够不用多线程(由于处理UI很是快),但不能不用异步(不然的话至少要等IO结束)。也就是说异步方法并不必定永远在新线程里面执行,反之亦然。 若是这样说,那何时多线程才会起到真正意义上的效率?何时该使用多线程进行程序开发?服务器

四. 何时使用多线程

首先来了解一下这个概念:“多线程的使用主要是用来处理程序‘在一部分上会阻塞’,‘在另外一部分上须要持续运行’的场合”。通常是根据需求,能够用多线程,事件触发,callback等方法达到。可是有一些方法是只有多线程能办到的就只有用多线程或者多进程来完成。 举个简单的例子,能理解就行。假设有这样一个程序, 1会不停的处理收到的全部TCP请求。对于每一个TCP请求作不一样的操做。不能有遗漏 2有不少特定的请求会向一个服务器发送存储的数据,或者是等待用户输入。 来看看。第1个要求很简单。用个while循环就搞定了。但第2个特性呢。一旦在等待用户输入或者是链接服务器时,程序会“阻塞”一段时间,这一段时间内就没法处理其余的TCP请求了。 因此能够利用多线程,每一个线程处理不一样的TCP请求。这样程序就不会“阻塞”掉了。 总之,凡事天然有利有弊,多线程带来了高效率的同时也带来了必定程度我不稳定性,上述内容只是在多线程自己的基础上得出的结论,若是综合线程安全,同步,通讯等状况去看就会变得更加负责。 总结一下就是,当应用在前台操做的同时还须要进行后台的计算或逻辑判断的状况可使用多线程,可是须要综合考虑使用多线程的不稳定性,尽可能避免因为使用多线程而产生的新问题。网络

五. iOS线程状态

通常来讲,线程有五个状态多线程

  • 新建状态:线程经过各类方式在被建立之初,尚未调用开始执行方法,这个时候的线程就是新建状态;
  • 就绪状态:在新建线程被建立以后调用了开始执行方法,可是CPU并非真正的同时执行多个任务,因此要等待CPU调用,这个时候线程处于就绪状态。处于就绪状态的线程并不必定当即执行线程里的代码,线程还必须同其余线程竞争CPU时间,只有得到CPU时间才能够运行线程。
  • 运行状态:线程得到CPU时间,被CPU调用以后就进入运行状态
  • CPU 负责调度可调度线程池中线程的执行
  • 线程执行完成以前(死亡以前),状态可能会在就绪和运行之间来回切换
  • 就绪和运行之间的状态变化由 CPU 负责,程序员不能干预
  • 阻塞状态:所谓阻塞状态是正在运行的线程没有运行结束,暂时让出CPU,这时其余处于就绪状态的线程就能够得到CPU时间,进入运行状态。
  • 线程经过调用sleep方法进入睡眠状态
  • 线程调用一个在I/O上被阻塞的操做,即该操做在输入输出操做完成以前不会返回到它的调用者
  • 线程试图获得一个锁,而该锁正被其余线程持有;
  • 线程在等待某个触发条件
  • 死亡状态:当线程的任务结束,发生异常,或者是强制退出这三种状况会致使线程的死亡。线程死亡后,线程对象从内存中移除。一旦线程进入死亡状态,再次尝试从新开启线程,则程序会挂。

七. iOS线程安全

一块资源可能会被多个线程共享,也就是多个线程可能会访问同一块资源,好比多个线程访问同一个对象、同一个变量、同一个文件和同一个方法等。所以当多个线程访问同一块资源时,很容易会发生数据错误及数据不安全等问题。所以要避免这些问题,咱们须要使用某些方式保证线程的安全,好比“线程锁”。

这边有一段代码,能够先用来测试一下:

@property (atomic, assign) int intA;

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        //thread A
        for (int i = 0; i < 10000; i ++) {
            self.intA = self.intA + 1;
            NSLog(@"Thread A: %d\n", self.intA);
        }
    });

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        //thread B
        for (int i = 0; i < 10000; i ++) {
            self.intA = self.intA + 1;
            NSLog(@"Thread B: %d\n", self.intA);
        }
    });
复制代码

即便将intA声明为atomic,最后的结果也不必定会是20000。缘由就是由于self.intA = self.intA + 1;不是原子操做,虽然intA的getter和setter是原子操做,但当咱们使用intA的时候,整个语句并非原子的,这行赋值的代码至少包含读取(load),+1(add),赋值(store)三步操做,当前线程store的时候可能其余线程已经执行了若干次store了,致使最后的值小于预期值。这种场景就是多线程不安全的表现之一。

为了更加安全的使用多线程,也为了代码能够正确的执行,咱们须要一种保证线程安全的机制,“线程锁”就诞生了,接下来将简单的了解一下iOS中的经常使用锁。 在网上查找了有些资料,发现大多数资料只介绍了如下几种锁:

  1. OSSpinLock (暂不建议使用,缘由参见这里
  2. dispatch_semaphore
  3. pthread_mutex
  4. NSLock
  5. NSCondition
  6. pthread_mutex(recursive)
  7. NSRecursiveLock
  8. NSCondition
  9. @synchronized

这张图片是在网上找到的关于这七种锁的性能测试:

固然除了这七种锁以外iOS还提供了不少其余的锁,好比C语言实现的读写锁(Read-write Lock),自旋锁(Spin Lock)等 这里就不作对锁的解释了,文章下边会有连接对各类锁在iOS中的应用作详细解释,还有配合iOS经常使用多线程方法写的一些小demo,对这方面有兴趣的能够去看一下。

六. iOS常见多线程使用方法

接下来就介绍一下iOS常见的几种多线程实现方式,由于篇幅比较长,因此写到另外几篇文章里边:

1. Pthreads

这篇文章主要是介绍Pthreads的,里边会有Pthreads的基础释义,也有经常使用API与属性的介绍,固然也会介绍一些经常使用锁和Pthreads的配合使用,连接在这Pthread。然而里边并不会针对所有的锁作解析,只是针对某几种锁作释义解析以及与Pthreads的配合使用。

2. NSThread

而后是NSThread这个,在网上找了不少资料,看了不少文章,可是老是不太符合本身的心意。恰好公司有机会让我参与多线程的调研,就试着总结了一下它的经常使用属性与API,也有一些加锁代码。

3. GCD

GCD的内容太多了,到发出此连接的时候已经修改过三次了。仍是有不少知识没有涉及到,但愿之后有时间补上吧。Grand Central Dispatch,这是GCD调研结果的连接。 ####4. NSOperation 全程看着官方文档写的,附带了一些应用实例,函数解析等NSOperation

以上,就是本人对多线程的调研结果。调研期间我看到这样一句话“咱们的目的不是研究出多么牛逼的锁,而是研究安全更高效的多线程方式”,固然,在没有没这样牛逼的多线程实现方式的时候,仍是有必要了解一下各类锁的优缺点的。

有志者、事竟成,破釜沉舟,百二秦关终属楚; 苦心人、天不负,卧薪尝胆,三千越甲可吞吴.