没错就是解毒
前言
GCD做为iOS开发中多线程管理的重要帮手。起初我只能说是用过,但是并没有较为仔细的了解它。最近又一次重新梳理了一下,虽然目前来看,还存在一些问题,无法解释。但还是先做个笔记,与大家分享下。同时,希望大家也能一起探索或者帮忙解答下这些问题:]
<p>
一些概念
首先,我们来解读一些基本的概念。在多线程开发中我们经常会遇到很多的名词,一时间也没办法分清他们到底是什么意思。所以我在这里做个简单的整理
并行 和 并发
并发: 统一执行很多任务,表面上同时发生,但是实际上是计算机分时处理的结果,即CPU在不同的任务之间进行切换,执行一段时间后切换到另一个线程。
并行:真正意义上的同时执行任务。
两者的区别实际上用一张图就能很好的说明
同步 和 异步
同步:等操作完成才返回
异步:直接返回
串行队列 和 并发队列
串行队列:一次只执行一个任务
并发队列: 一次执行多个任务
这里我们需要说明的是,无论是并发队列还是并行队列,他们都是FIFO的,也就是说先添加到队列中的任务会先被执行,但是对于并发队列而言,我们不能保证执行完成时的顺序。另外,在一些博文中会将Concurrent Queue翻译为 并行队列,个人认为并不是很严谨。首先是因为Concurrent 本意就是并发,并且如果使用并发这个名词,对于一些初学者来讲可能会产生误导,队列中的任务到底是并发执行还是并行执行的呢?
进程 和 线程
进程:是操作系统对于正在运行的程序的一种抽象,在系统上可以同时运行多个进程,而每个进程都好像在独立使用硬件
线程:一个进程实际上可以由多个线程的执行单元组成,每个线程都在进程的上下文中,共享同样的代码和全局数据
进程、线程和应用程序的关系:进程为应用程序开辟内存空间,而线程执行应用程序代码
进程和线程的关系:进程由线程组成、一个进程理论上可以有很多的线程,但至少有一个主线程。
死锁
死锁: 两个或多个线程相互等待而导致任何一个线程都不能执行。
概念汇总
关于一些问题的想法
我们经常会看到以下代码
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
//[self do something];
});
这段代码表示我们“开启一个异步线程,在全局队列中执行”
那么我们如果我们将dispatch_get_global_queue()
函数改为dispatch_get_main_queue()
就会说我们在主线程上做处理事务,而事实上我们使用这个函数创建或得到的是一个队列而非线程。
更准确的说,我认为,我们使用GCD的时候应该是面向队列,而不是线程。在很多的博客中都会使用NSLog(@"current thread is %@", [NSThread currentThread])
这段代码来打印当前的线程,然后告诉你说我们在XX线程上执行了代码。的确,这样的方式更加直接的说明了问题。但是就像我之前提到的,开发者应该面向的是队列而不是线程,所以这只是辅助说明的一种方式。
OK,接下来我们讨论下dispatch_async和dispatch_sync函数都干了些什么。开始我以为他们是创建线程用的,就如一些的博文中会讲到,“每执行一次任务的时候,dispatch_async总会为我们开辟一个新的线程”
真的是这样吗?
我在文档里看到如下说明
dispatch_sync
Submits a block object for execution on a dispatch queue and waits until that block completes.
Submits a block to a dispatch queue for synchronous execution. Unlike dispatch_async, this function does not return until the block has finished. Calling this function and targeting the current queue results in deadlock
dispatch_async
Submits a block for asynchronous execution on a dispatch queue and returns immediately.
This function is the fundamental mechanism for submitting blocks to a dispatch queue. Calls to this function always return immediately after the block has been submitted and never wait for the block to be invoked. The target queue determines whether the block is invoked serially or concurrently with respect to other blocks submitted to that same queue. Independent serial queues are processed concurrently with respect to each other.
这里我认为,这两个函数只是用于提交任务或者说block的。他们唯一的区别是会不会等待任务完成。对于dispatch_sync
而言,他会提交任务,同时阻塞当前的线程直到任务执行完成才会返回。 dispatch_async
则会在提交任务之后直接返回,不阻塞和等待。如此一来,真正和与线程相关的就可能是队列了。
另一种视角
前面提到的在GCD中我们只是面向队列和任务。队列包括串行队列和并发队列,而任务提交是同步或者异步的。那么我们就来看下这些概念排列组合后的场景。
同步提交 + 串行队列
说明:在调用dispatch_sync方法的时候我们指定了添加任务的队列和任务(block)。那么该方法就会提交该任务到串行队列中,然后然后阻塞线程,等待任务完成之后再返回。对于串行队列,前面的概念解释里已经说过了,任务一个接一个执行。
异步提交 + 并发队列
说明:异步提交的时候,dispatch_async函数提交然后直接返回。区别是并发队列会一次执行多个任务。
同步提交 + 并发队列
异步提交 + 串行队列
代码部分
我们在从代码部分去理解同步和异步的问题
左侧两段输出,我们可以看到,都是同步提交,无论是提交到串行队列还是并发队列,执行的顺序都是一样的。并且提交任务前后的代码的顺序都是依次执行的。
在看右侧部分,由于异步提交后直接返回不等待执行完成。所以任务提交前后的代码会先于任务中的代码执行。
接下来,我们在从代码的角度去理解串行队列和并发队列
并发
在同样是异步的前提下,任务前后的代码都先于任务执行了(当然这并不是绝对的)。我们看到在串行队列中,执行任务的线程不在主线程中,但是他们都是在一个线程中去处理。也正因为任务前后的代码执行和任务代码执行的线程不同,所以我们并不能保证任务部分的代码和任务后的代码的执行先后性。因此会出现任务代码先执行,在执行过程又出现任务后的打印输出。
同样是同步提交的情况下,两个输出完全一致。串行队列同步提交的情况我们可以理解,因为它一次只执行一个任务,所以线程号是一样的。但是,并发队列的情况下,该怎么理解?其实也很简单,因为他是同步提交,需要等到当前任务完成后返回再进行后续的操作,所以即使是并发的队列,我们看到的效果还是和串行一样。
遗留的问题
首先很感谢你能看到这里,听我瞎扯那么多。
以上是我个人的理解,感觉与一些博文中的思想有些出入,所有我也不是很确定。我仅以
我看到的为参考,并提出自己的理解。
目前还有以下问题暂时没有想通:
1)队列和线程的关系
从文档中有看到系统实际上有维持一个线程池,但是没有说清队列和它有什么具体的关系。
2)队列和主线程的关系
如下图,同样是在主线程中执行,但是如果将这个queue改成main queue时就会报错。
归根结底还是没有理清线程和队列的关系,或者说是主线,队列,其它线程这三者的关系。
如果觉得我的想法有问题的同学,请帮忙指正,万分感谢。
如果觉得我理解有道理的同学,希望能一起探讨一下遗留的问题,共同完善知识体系:]
最后放上源代码
GCD解毒Demo