有一段时间没有更新博客了,最近APP有了新需求,比较忙,今天来给大家说一下在IOS中使用多线程的相关知识。首先大家要明确什么是线程什么是进程,简单的说一个进程就是一个已经打开程序,比如你的手机现在打开了微信,同时也打开了QQ,那么在不考虑系统进程的情况下,你的手机现在就有两个进程,一个是微信进程,一个是QQ进程;线程就是在某个进程内所执行的任务,打个比方说你在微信中聊天,处理你聊天的那些代码就运行在一个进程中,可以说每一个进程(App)中正在执行的一个任务、功能就是线程,一个进程中最少有一个线程,线程也是进程操作的最基本单元。比如你再微信中一边聊天,一边接受语音,那么此时此刻就有两个线程再运行,一个是文字聊天进程,另一个就是语音线程。在iOS中实现多线程有好几种方法:
1、NSThread
优点:NSThread 比其他两个轻量级。
缺点:需要自己管理线程的生命周期,线程同步,线程同步时对数据的加锁会有一定的系统开销。
2、Cocoa NSOperation (使用NSOperation和NSOperationQueue)
NSOperation 是苹果公司对 GCD 的封装,完全面向对象,所以使用起来更好理解。 大家可以看到 NSOperation 和 NSOperationQueue 分别对应 GCD 的 任务 和 队列
3、GCD (Grand Central Dispatch)
这个是苹果自己的多线程策略,在他的官方文档上说是最好用的方法,使用非常方便,完全自动管理线程的生命周期,也能充分的利用多核cpu的性质,最重要的是它会自动管理线程的生命周期(创建线程、调度任务、销毁线程),使用了大量的block封装。也是本文主要讲的内容
4、Pthreads
简单地说,这是一套在很多操作系统上都通用的多线程API,所以移植性很强(然并卵),当然在 iOS 中也是可以的。不过这是基于 c语言 的框架,使用起来这酸爽!
进入主题
先说一下我们会使用到多线程的几个场景,为一会举例子做铺垫。首先大家知道我们在运行一个程序或者使用一个功能的时候操作系统会进行大量的运算,比如循环、遍历、之类的,或者受外部的影响导致消耗很长时间,比如网络请求、加载数据。多线程所做的操作就是将这些耗时的操作放到一个除主线程之外的新开辟的一个线程中,然后将该线程放到后台运行。这样就不会造成主线程的阻塞,使主线程依然流畅。在iOS系统中主线程通常是放有大量的UI显示,动画效果,所以不管你进行多复杂的运算逻辑,或是网络多卡,界面一样流畅,只是没有显示出处理后的结果罢了(结果会在数据处理线程完成后回调出来),这也是为什么iOS的流畅度会甩Android几条街。下面我们用一个例子来展示,
程序在运行后会自动创建一个主线程,看下面的代码:
程序运行开始系统创建了主线程,然后主线程开始运行,在执行到25行的时候我在主线程中执行了一个非常耗时的操作从网上加载了999次图片(虽然很无聊,但是毕竟是小例子),然后大家下面的时间系统花了16秒左右去执行这个操作,此时主线程一直在等待该操作执行完成,完成后主线程才继续往下走直到结束。虽然16秒的时间还勉强可以接受,但是这毕竟是个小demo代码非常简单,试想一下如果在一个大的工程中,到处都充满着复杂的算法,如果出现线程阻塞的话一定是非常可怕的,所以我们需要用多线程去解决,记住一个思想 将非常耗时的操作丢到另一个线程,让他不影响主线程的运行。再看如下代码:
在代码中加了一个block,然后看下面的输出,主线程运行完了之后过了将近1分钟(这次时间长是因为突然网络不好,对实验没有影响),我们看出了这次这个耗时的操作并没有影响到主线程的执行,好像是等主线程完事后在执行的,其实并不是这样的,图中第一部分的block就是GCD的一个代码片段,将耗时的操作丢到了另一个新开辟的线程去做,然后新线程去处理耗时操作的同时,主线程继续向下执行。在GCD中有两个概念,一个是任务,另一个是队列:

程序运行开始系统创建了主线程,然后主线程开始运行,在执行到25行的时候我在主线程中执行了一个非常耗时的操作从网上加载了999次图片(虽然很无聊,但是毕竟是小例子),然后大家下面的时间系统花了16秒左右去执行这个操作,此时主线程一直在等待该操作执行完成,完成后主线程才继续往下走直到结束。虽然16秒的时间还勉强可以接受,但是这毕竟是个小demo代码非常简单,试想一下如果在一个大的工程中,到处都充满着复杂的算法,如果出现线程阻塞的话一定是非常可怕的,所以我们需要用多线程去解决,记住一个思想 将非常耗时的操作丢到另一个线程,让他不影响主线程的运行。再看如下代码:

任务:在 GCD 中就是一个 Block,所以添加任务十分方便。任务有两种执行方式: 同步执行 和 异步执行,他们之间的区别是
是否会创建新的线程。
同步执行:只要是同步执行的任务,都会在当前线程执行,不会另开线程。
异步执行:只要是异步执行的任务,都会另开线程,在别的线程执行。
同步(sync) 和 异步(async) 的主要区别在于会不会阻塞当前线程,直到 Block 中的任务执行完毕!
如果是 同步(sync) 操作,它会阻塞当前线程并等待 Block 中的任务执行完毕,然后当前线程才会继续往下运行。
如果是 异步(async)操作,当前线程会直接往下执行,它不会阻塞当前线程。
队列:
用于存放任务。一共有两种队列, 串行队列 和 并行队列。
注:FIFO(先进先出)
串行队列 一个接一个的先进先出的进行执行。
放到串行队列的任务,GCD 会 FIFO(先进先出) 地取出来一个,执行一个,然后取下一个,这样一个一个的执行。
并行队列 中的任务 根据同步或异步有不同的执行方式。
放到并行队列的任务,GCD 也会 FIFO的取出来,但不同的是,它取出来一个就会放到别的线程,然后再取出来一个又放到另一个的线程。这样由于取的动作很快,忽略不计,看起来,所有的任务都是一起执行的。不过需要注意,GCD 会根据系统资源控制并行的数量,所以如果任务很多,它并不会让所有任务同时执行。
主队列
系统都会默认生成有一个主队列(串行队列)一些UI方面的操作系统都会在主队利中进行,
dispatch_queue_t
queue =
dispatch_get_main_queue
();
以后我们用到多线程时候如果遇到刷新界面UI的操作也一定都要回归到主队列里来执行。

全局并行队列
系统唯一一个并行的队列,凡是需要并发的任务都放在这个队列里就行(如果你任性非要自己创建也行)就像我刚才写的代码一样吧耗时的操作放到这个队列里
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

自定义队列(自己创建的队列)
dispatch_queue_t customQueue = dispatch_queue_create("testQueue", NULL);

其中第一个参数是标识符,用于 DEBUG 的时候标识唯一的队列,可以为空。第二个参数用来表示创建的队列是串行的还是并行的,传入 DISPATCH_QUEUE_SERIAL 或 NULL 表示创建串行队列。传入 DISPATCH_QUEUE_CONCURRENT 表示创建并行队列。
创建任务
使用全局并行队列创建个异步执行任务

使用全局并行队列创建个同步执行任务

使用自定义队列创建个异步执行任务

使用自定义队列创建个同步执行任务

队列组
这个东西说实话平时我用的不是很多,这里就简单说下:
队列组可以将很多队列添加到一个组里,这样做的好处是,当这个组里所有的任务都执行完了,队列组会通过一个方法通知我们。下面是使用方法。
dispatch_group_t group = dispatch_group_create(); ---------创建队列组

最后要注意
1:比如我加载完图片要addsubView了,那么这个addSubView方法要放在主线程中使用像这样
2:看如下这段代码
看似没什么问题,其实会造成线程阻塞,因为首先这是同步执行的,主线程只会在这个testMethod方法执行完之后才会执行,也就是暂时阻塞的主线程,但是我们看到这里用的是get_main_queue也就是又向主线程中添加了这个比较耗时的任务,但是此时主线程已经被阻塞了,所以并不能完成这个任务,导致一直堵着。所以这段代码走到32行就停止了。


看似没什么问题,其实会造成线程阻塞,因为首先这是同步执行的,主线程只会在这个testMethod方法执行完之后才会执行,也就是暂时阻塞的主线程,但是我们看到这里用的是get_main_queue也就是又向主线程中添加了这个比较耗时的任务,但是此时主线程已经被阻塞了,所以并不能完成这个任务,导致一直堵着。所以这段代码走到32行就停止了。