从死锁角度窥探GCD

作者: 和珏猫 | 来源:发表于2016-12-08 16:22 被阅读232次

           最近一直没有更新简书是因为在开发和测试阶段,有任务,没有进行学习,不过在做任务的时候也遇到了一些技术点,在这里总结一下。

           今天我们来深度学习GCD。深度学习我们可以理解为进阶学习,该文试图用demo里面的栗子形象学习GCD的用法。

          其实在我们平时的开发过程中,这个GCD甚至多线程用的并不多,就像算法,我们在开发过程中用的并不多,但是这些知识确实是考验我们基础是否扎实的一个标准,还是之前说过的:java和OC或者别的语言,只是语言形式不同罢了,实际运用的计算机思想是通用的。所以我们通过学习GCD,可以以此为突破口,进而熟知多线程相关的知识。“管中窥豹,可见一斑”:比喻从观察到的部分,可以推测全貌。类似的我们举一反三:“冰山一角,可见冰山”,哈哈哈。。。

         1、死锁

        昨天看到一个面试题,题目如下:

            通过上边的解释我们可以看出,主线程中同步插入一个主线程的队列,会造成主线程等着插入的线程完成后才会进行,而要插入的线程完成,那么主线程必须完成,这样就形成了,你中有我我中有你互相引用的问题,就好像循环引用似的,互相牵制,结果大事未了,含恨而终。。。

            那么我们如果插入的线程不是主线程呢,我们试试看。

           这里我在打印4之后,给主线程插入一个同步队列queue1,这样的话,主线程会被阻塞,等待dispath_sync的block执行完成后再继续执行,打印出5来了,然后主线程继续执行打印6。

    我们再来看下边的这段代码:

    结果在上图,只打印了4和5,我们分析这段代码:打印4,同步之行queue1队列,打印5,再同步执行queue1队列,我们知道dispath_sync是需要等待block里面的结果出来后再进行下边的执行步骤的,这里造成死锁:后边的queue1等待前边的queue1执行完成后才进行后边的步骤,但是前面的queue1也在等待后边的queue1执行完成,这样造成死锁。

    由此我们可以得出:dispath_sync不能执行所在的线程的线程,不能执行所在队列中的队列,不然就会造成死锁。

    我们再来看下边的代码:

    这里我们更能理解async和sync的区别:

    这里如果我不打断点,会一直打印4,而且我们看左上角那里会看到CPU消耗时99%,电量显示时high。

    我们这时候来看一些概念性的知识:

    2、什么是GCD、GCD的优势

    (1)GCD全称是Grand Central Dispatch,可以为“伟大的中枢调度”。纯C语言,提供了非常多且强大的函数

    (2)优势

    GCD是苹果公司为多核的并行运算提出的解决方案

    GCD会自动利用更多的CPU内核(比如双核、四核)

    GCD会自动管理线程的生命周期(创建线程、调度任务、销毁线程)

    程序员只需要告诉GCD想要执行什么任务,不需要编写任何线程管理代码

    3、什么是任务和队列

    GCD中有2个核心概念

    (1)任务:执行什么操作

    (2)队列:用来存放任务

    GCD的使用就2个步骤(1)定制任务(2)确定想做的事情

    将任务添加到队列中,GCD会自动将队列中的任务取出,放到对应的线程中执行

    提示:任务的取出遵循队列的FIFO原则:先进先出,后进后出

    4、执行任务

    1)GCD中有2个用来执行任务的函数,即同步方式、异步方式执行任务

    dispatch_sync(dispatch_queue_t queue,dispatch_block_t block);

    dispatch_async(dispatch_queue_t queue,dispatch_block_t block);

    block里面的东西,就是你准备交给队列queue处理的任务

    2)同步、异步、串行、并发

    同步和异步决定要不要开启新的线程

    同步:在当前线程中执行任务,不具备开启新线程的能力

    异步:在新的线程中执行任务,具备开启新线程的能力

    串行和并行决定任务的执行方式

    串行:一个任务执行完毕后,再执行下一个任务

    并发:多个任务并发(同时)执行

    5、怎么获取线程

    1)创建队列方法

    dispatch_queue_create(const char *label, dispatch_queue_attr_t attr)   //创建队列,在非ARC中要手动释放。这个方法既可以创建串行队列,也可以创建并发队列

    注意:第一个参数是字符指针,表示队列名称,第二个是队列的属性(传NULL或DISPATCH_QUEUE_SERIAL都表示的是串行队列,传DISPATCH_QUEUE_CONCURRENT,表示并发队列)

    dispatch_get_main_queue();   //使用它时,要注意死锁,因为不论是同步还是异步执行他,都不会开辟新的线程,他会在主队列执行

    dispatch_get_global_queue(long identifier, unsigned long flags)   //并发队列,第一个参数是设置优先级的,后一个参数是为将来使用,可以传0

    从上面可以看出:queue如果是串行队列,那么,会开辟一个新的线程,但是,是前一个执行完毕,再执行后一个。并发队列会开启两个线程,并发执行。

    讨论:哪些会死锁?死锁的原因是什么?开辟了新线程么?

    <1>同步执行同一个串行队列

    <2>异步执行同一个串行队列

    <3>同步异步执行同一个串行队列

    <4>异步同步执行同一个串行队列

    <5>异步执行不同串行队列

    <6>同步执行主队列

    <7>同步执行不同串行队列

    <8>异步同步执行不同串行队列

    <9>同步异步执行不同串行队列

    <10>同步执行不同并发队列

    <11>同步异步执行同一个并发队列

    <12>异步同步执行同一个并发队列

    <13>异步执行不同并发队列

    <14>异步执行同一个/不同并发队列

    6、总揽

    1、图片总体看看GCD

    延时方法举个栗子,看时间:

    注: dispatch_after 函数并不是在指定时间后执行处理,而是在指定时间后追加处理到Dispatch Queue。例如Main Dispatch Queue在主线程的RunLoop中执行。所以在比如每隔1/60秒执行的RunLoop,Block最快在3秒后执行,最慢在 3+1/60秒后执行。所以上图中,performSelector和NSTimer的延时执行比我们设定的2秒和4秒分别晚了4毫秒和3毫秒,但是我们再看dispatchAfter的延时执行,晚了0.582秒,即582毫秒。

    2. dispatch_barrier_async的作用是什么?(dispatch栅栏)

    在并行队列中,为了保持某些任务的顺序,需要等待一些任务完成后才能继续进行,使用 barrier 来等待之前任务完成,避免数据竞争等问题。 dispatch_barrier_async 函数会等待追加到Concurrent Dispatch Queue并行队列中的操作全部执行完之后,然后再执行 dispatch_barrier_async 函数追加的处理,等 dispatch_barrier_async 追加的处理执行结束之后,Concurrent Dispatch Queue才恢复之前的动作继续执行。

    打个比方:比如你们公司周末跟团旅游,高速休息站上,司机说:大家都去上厕所,速战速决,上完厕所就上高速。超大的公共厕所,大家同时去,程序猿很快就结束了,但程序媛就可能会慢一些,即使你第一个回来,司机也不会出发,司机要等待所有人都回来后,才能出发。 dispatch_barrier_async 函数追加的内容就如同 “上完厕所就上高速”这个动作。

    举个栗子:猫8888是设置的栅栏

    通过结果我们可以看到:猫8888之前的无序的,后边的也是无序的。在多个并行处理之间插入指定处理后再继续多个并行处理。

    3、 Dispatch Group

    如果想要在追加到多个Dispatch Queue中的多个处理全部结束后执行结束处理,可使用Dispatch Group。

    例如:

    举个栗子:

    参考:GCD用法详细介绍

               招聘一个靠谱的iOS(下)

    最后,哪里不对的地方可以给我留言,我会及时改进的,谢谢大家。

    相关文章

      网友评论

        本文标题:从死锁角度窥探GCD

        本文链接:https://www.haomeiwen.com/subject/hgojmttx.html