最后一次学习gcd

2018-03-12  本文已影响16人  8fe8946fa366

1.什么是gcd

gcd是异步执行任务的技术之一,gcd中线程的生命周期不需要程序员手动管理,由系统自动管理。

2.多线程编程

一个单核的CPU在同一时间只能执行一个任务(线程),多线程的实现是通过线程间频繁的进行上下文切换而模拟了多线程的效果,并不是真的有多个任务同时执行。

然而多核的CPU就真的可以做到多个任务同时执行(线程)。

多线程编程容易造成很多问题:

1.数据竞争  多个线程同时更新相同的资源会导致数据不一致

2.死锁 停止等待的线程会导致多个线程间相互等待造成死锁

3.消耗内存 开启太多子线程会消耗大量的内存✨

那么多线程有这么多问题为什么还要用呢:

为了保证程序的响应性能,如果所有的耗时操作都放在主线程执行就会阻塞住线程runloop的执行,会造成界面的卡顿。开启子线程去执行耗时操作就可以提升系统的响应性能。

3.GCD中的两种队列

Serial Dispatch Queue串行队列  串行队列中的任务是按照先进先出的顺序执行的,同时只能执行一个任务。

⚠️:生成多个Serial Dispatch Queue串行队列的时候,多个串行队列会并行执行。🌰:有四个串行队列,每个队列里追加一个任务,四个串行队列并发执行,相当于四个任务并发执行。在一个串行队列里追加多个任务,多个任务是按顺序串行执行的。

只要生成一个串行队列就会开启一个线程(异步函数),所以不要用串行队列去开启过多的线程。

Concurrent Dispatch Queue 并行队列 并行队列中的任务并发执行,不按顺序,如果是异步函数可以开启子线程,开启子线程的个数和任务在哪个线程里执行都是由系统决定的。执行任务的顺序会根据处理内容和系统状态发生改变。

⚠️:dispatch_async(queue0, ^{

        NSLog(@"duty1---%@",[NSThread currentThread]);

    });

用这个方法往队列里添加任务的时候,一个block代表一个任务,不管是什么队列一个任务肯定是一起执行的,并且在一个线程里。多次使用这个方法添加任务,添加的才是不同的任务,这个时候同一个队列里任务的执行顺序就发生了变化,如果是串行队列任务就串行执行,如果是并发队列任务就并行执行。

系统提供的队列:

主队列

全局并发队列

4.两种函数

同步函数 dispatch_sync 阻塞 

异步函数 dispatch_async 非阻塞

主线程+同步函数+串行队列(包括主队列) =  死锁

5.dispatch_after函数

如果想要让某个任务在一段特定的时间之后执行,可以使用dispatch_after方法。他是让block中的任务在特定的时间后追加到响相应的队列里。

⚠️:这里延迟执行的时间并不是精确的,不是在一段时间后执行任务,而是在一段时间后把任务添加到队列里,至于什么时候执行要看runloop的执行间隔。

这个方法其实相当于在一段时间后用dispatch_async方法把任务追加到队列里。

6.dispatch_group

适用场景:把多个并发执行的任务合为一组,调用者就可以知道这个组里的所有任务什么时候能够全部执行结束。

🍰:好处就是既可以让任务并发执行,又可以检测到多个并发任务执行结束的状态。

dispatch_group_async(group, queue, ^{ NSLog(@"opt1---%@",[NSThread currentThread]);});//把任务添加到队列组里,多次调用添加多个任务

等待队列组任务执行完毕的两个方法:

void dispatch_group_notify(dispatch_group_t group,dispatch_queue_t queue,dispatch_block_t block);

⚠️:这个函数是异步的,不会阻塞当前线程。

这个方法可以检测到队列里所有任务执行的结束,一旦检测到结束,就可以将结束的处理(dispatch_group_notify里的block)追加到队列(queue)里。这样就可以设计为队列组里的所有任务执行完毕之后收到通知,或者执行某些操作。

long dispatch_group_wait(dispatch_t_group group,dispatch_time_t timeout)

⚠️:dispatch_group_wait这个方法是阻塞的,只有到了等待的时间,或者是队列中的任务在等待时间内提前完成了这个函数才会返回,否则就会一直阻塞当前线程。

⚠️:这个方法是有返回值的,返回值是long类型。

dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW,full*NNSEC_PER_SEC);

long result = dispatch_group_wait(group,time);

result等于0,代表在time时间之内,group中的任务全部执行完毕。 如果result不等于0,就代表等待时间内还有任务没有执行完毕。

7.dispatch_barrier_async

今天我才知道了这个方法真正的作用!!!

我们知道,同时在多个线程对一个文件执行写操作或者写操作和读操作会造成数据的错乱。但是并行的执行读操作是没有问题的,而且还会提升读取的效率。这个时候dispatch_barrier_async方法就派上了用场。

 dispatch_async(queue,blk_forreading0);

dispatch_async(queue,blk_forreading1);

dispatch_async(queue,blk_forreading2);

dispatch_barrier_async(queue,blk_forwriting);

dispatch_async(queue,blk_forreading3);

dispatch_async(queue,blk_forreading4);

dispatch_async(queue,blk_forreading5);

使用dispatch_barrier_async方法可以保证,上面的读取任务并发执行完毕之后再执行写的任务,然后再并发执行读取操作。

⚠️:dispatch_barrier_async方法里使用的queue,必须是手动创建的并发队列,不可以是全局并发队列!!!

这个思路可以用来去设计线程安全的属性,设计线程安全的get方法和set方法。

52条这本书的第41条里写到,应该少用同步锁,多用派发队列。

就是说当我们想要保证一个属性是存取安全的时候,最好不要使用同步块(synchronize)也不要使用atomic,原因是效率很低,而且同步块容易造成死锁。

8. dispatch_apply快速迭代

快速迭代可以按照指定的次数,把block多次添加到队列中。

dispatch_queue_t queue = dispatch_get_global_queue(0, 0);

    dispatch_apply(10, queue, ^(size_t index) {

        NSLog(@"%zu",index);

    });这个block是有参数的,为了按照第一个参数重复追加并且区分block

⚠️:dispatch_apply这个函数是阻塞的,为了不影响当前线程,可以把他放到dispatch_async方法里去执行。

10.dispatch_suspend/dispatch_resume

这两个方法的参数都是队列,queue。

⚠️:这两个方法对于已经执行的任务没有影响,比如说有一个任务正在执行没那么调用dispatch_suspend方法,会等到这个任务执行完以后再暂停后面尚未处理的任务。

11.dispatch_semaphore

信号量可用于保证线程安全,只有0和1两个值的信号量叫做二进制信号量,有多个值的信号量叫做通用信号量。

信号量的值代表了相应资源的使用情况,信号量的值大于0代表当前可用资源的数量。信号量的值小于0时,它的绝对值代表当前等待使用该资源的线程数量。

创建信号量:

dispatch_semaphore_t semaphore = dispatch_semaphore_create(1);

初始化时计数为1.

dispatch_semaphore_wait(semaphore,DISPATCH_TIME_FOREVER);这个函数肯定是阻塞的

计数大于等于1时不等待并返回该函数,并且计数减1,计数等于0时等待。

返回值是long类型,如果返回结果为0,代表semaphore大于等于1,执行了该函数,返回的不是0,代表semaphore等于0,在到达指定时间为止待机。

dispatch_semaphore_signal(semaphore);这个方法是把semaphore加1。

12.dispatch_once

单例模式:在整个程序的运行过程中,一个类只会生成一个实例对象。

dispatch_once用于生成单例模式,代码块只执行一次,而且是线程安全的。

void dispatch_once(dispatch_once_t *token,dispatch_block_t block);

那么这个函数是根据dispatch_once_t这个数据类型token来标记的,也就是说一个token对应一个block块,那么一个相同的token对应的block块只会执行一次,也就是说为了让block块在程序运行中只能执行一次,那么这个token在程序运行中必须是相等的,所以应该声明为静态变量或全局变量

使用dispatch_once写单例方法的正确姿势:

+(id)sharedInstance{

static EOCClass* sharedInstance = nil;

static dispatch_once_t onceToken;

dispatch_once(&onceToken,^{

sharedInstance =[ [self alloc]init];

});

return sharedInstance;

}

上一篇下一篇

猜你喜欢

热点阅读