在开发的过程中,对于多线程的需求越来越高,但是由于在概念的理解上一直都有些许的模糊不清,所以这次也趁着重读一遍高级编程中的GCD部分,将多线程重新整理了一下。
1. 多线程的理解
2. GCD方法的理解
1. 多线程的理解
多线程的本义很好理解:就是为了多个不同的任务开启多个线程,让主线程能及时的响应用户的操作。但是对于和多线程紧紧相关的异步、同步和并行、串行而言,很容易搞模糊。所以下面还是按照自己的思路一步步进行分析:
1)队列
官方点的理解:队列就是任务的集合,采用的是先进先出的原则,每执行完一个任务就会释放一个任务。通白点理解就是:对于每一个新任务而言,先排到队伍的末尾,具体该如何执行等按序轮到才知道。
2)同步与异步
① 同步:在当前线程中执行任务,并没有开启新线程的能力
② 异步:具有开启新线程的能力,能在新的线程中执行任务
首先,通过图例解释异步执行的过程:
异步执行并不会堵塞当前的线程,只是告诉当前的线程:
你只要帮我将任务加入queue队列中即可,任务该如何执行无你无关。
所以从上面图例中的主线程视角看整个的执行过程,就是:
主线程并不会直接看到block中关于任务的内容,只是将dispatch_async和NSLog都当做一行执行命令来执行。也就是说:主线程只管执行各个命令行,而队列中加入的任务在其他线程中执行:
同步执行正好和异步执行相反,以下面的图例分析:
不仅需要将任务加入到queue队列中,还会强制要求当前线程暂停,来执行该队列中的任务。
所以结果和执行的顺序一致:
3)队列类型:串行和并行
① 串行 --->一个任务执行完再执行下一个任务,按序一个个执行。
dispatch_queue_t queue = dispatch_queue_create("test", DISPATCH_QUEUE_SERIAL);
① 并行 --->不等待当前任务执行完就执行下一个任务。
dispatch_queue_t queue = dispatch_queue_create("test", DISPATCH_QUEUE_CONCURRENT);
同步还是异步、串行还是并行对于任务而言就相当于两个同等重要的原则。用商店的收银台为例来理解:
一家商店的收银处,同步表示只有一个收银台,而异步表示有多个收银台。而串行表示管你有几个
收银台,所有人就排成一队;而并行表示尽量分散,有几个排几个。
在对多线程的处理过程中,同步执行其实是很容易出错的地方。个人理解就是:在当前线程正在处理“将任务加入queue队列”中时,同步执行会强制性的要求当前线程暂停手中的任务,从而用所有的资源来执行"queue队列中的任务"。举例而言:
主线程正在执行任务时,强制要求执行block中的新任务。
异步执行创建的线程正在执行任务,强制要求执行同步中的任务。
所以在对同步执行的使用中,要特别注意不要进入到崩溃的逻辑中。
2. GCD方法的理解
dispatch_after
对于该方法而言,有两个地方需要特别注意:
① NSEC_PER_SEC
dispatch_afer方法中的第二个参数的时间单位并不是秒而是"纳秒",所以这也是为什么需要使用系统定义的变量进行转换的原因。系统提供的装换方式:
每秒有多少纳秒
#define NSEC_PER_SEC 1000000000ull
每微妙有过少毫秒
#define NSEC_PER_MSEC 1000000ull
每秒有多少毫秒
#define USEC_PER_SEC 1000000ull
每毫秒有多少纳秒
#define NSEC_PER_USEC 1000ull
②4秒之后执行?
dispatch_after并不是在指定时间后执行处理,而只是在指定时间内将任务追加到Queue队列中。
对于上面的图例而言,该队列是在主线程的RunLoop中执行,而主线程的RunLoop每隔1/60秒执行,而该Block中的任务要求延迟4秒之后执行,所以最快在4秒 +1/60后执行。并且由于主队列中有大量的处理或主线程本身就有一定的延迟,所以执行的时间会更长。
所以,dispatch_afer只适用于一些想大致延迟执行的处理中,并不适用于对时间有严格要求的情况下。
dispatch_barrier_async
对于多线程而言,最需要关注的就是数据竞争的问题。虽然串行执行能避免该问题,但是现实生活中数据的读取往往是要求能并行执行的,只有当遇到对数据的写入时才需要额外的关注。
举例而言:
很显然,读取和写入并行的情况下,读取的数据会出现错误:
而dispatch_barrier_async的定义就是:
等待之前的并行执行全部结束之后,再将指定的处理追加到队列中。等待该函数追加的处理执行完毕之后,队列才会恢复到一般的动作:
所以,dispatch_barrier_async就是等待之前的并行操作执行之后,再单独的执行该函数中的任务处理,完毕之后就是普通的执行。
Dispatch Semaphore
在实际的开发过程中,经常会出现一种需求:在同一个界面中需要多次请求,在所有请求结束之后再刷新界面。在此之前,我一直都很low的用在一个请求结束时嵌入另一个请求的方法,直到认识到该方法:
上面的图例有几个需要知道的地方:
①dispatch_semaphore_t
Dispatch Semaphore是持有计数的信号,计数为0时等待,计数为1或大于1时,减去1而继续执行。
首先,创建信号时赋值计数的初始值为0
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
其次,当信号量遇到wait方法时,判断计数是否大于等于1。如果等于0,进入等待状态,所以等价于该线程暂停在该处。所以:
[task resume]必须写在wait方法之前
最后,当任务执行结束,signal函数会将计数值加1,此时wait函数会识别到计数不为0,会减1继续向下执行。
dispatch_semaphore_signal(semaphore);
信号量配合group使用,当所有的请求结束时,通知主线程进行界面的刷新:
对于类似多个网络请求的情况,GCD还提供了另外的一种方法:
dispatch_group_enter(group);
dispatch_group_leave(group);
关于越底层的东西,越值得深入的思考。GCD的理解也算是基本的入门,也想要更多的去了解这些东西。冬天到了,得好好学习了。