最近一直没有更新简书是因为在开发和测试阶段,有任务,没有进行学习,不过在做任务的时候也遇到了一些技术点,在这里总结一下。
今天我们来深度学习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用法详细介绍
最后,哪里不对的地方可以给我留言,我会及时改进的,谢谢大家。
网友评论