GCD介绍
Grand Central Dispatch (GCD)是Apple开发的一个多核编程的解决方法。
GCD 以block为基本单位,一个block中的代码可以为一个任务。下文中提到任务可以理解为执行某个block
GCD有两大重要概念,分别是队列和执行方式;使用block的过程,概括来说就是把block放进合适的队列,并选择合适的执行方式去执行block的过程。
GCD有三种队列(遵循的是先进先出,FIFO):
- 串行队列(每次只执行一个任务)
- 并发队列 (可以形成多个任务并发)
- 主队列 (这是一个特殊的串行队列,而且队列中的任务 一定会在主线程中执行)
两种执行方式:
- 同步执行
- 异步执行
死锁
造成死锁的原因是:GCD遵循FIFO的原则,所以如果在同一线程,同一队列中出现:后添加的任务需要先执行,而需要执行的任务却只能等后添加的任务执行完之后才能执行,便发生了死锁
NSLog(@"1"); // 任务1
dispatch_sync(dispatch_get_main_queue(), ^{
NSLog(@"2"); // 任务2
});
NSLog(@"3"); // 任务3
以上的任务均发生在主线程里,首先在主队列里添加任务1 任务3 当执行到同步线程这里,同步线程将任务2也添加到主队列里,并且根据FIFO原则,需要等已经在主队列的任务3执行完之后才能执行任务2,但是由于是同步的原因,想要执行任务3又必须等任务2执行之后,才能执行任务3 ,这样任务2,3处于相互等待的僵局中,变造成了死锁
GCD的常见应用:
- GCD定时器(不受RunLoop约束,比NSTimer更加准时)
dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
- 单例模式(线程安全,使用简单 )
static dispatch_once_t once;
dispatch_once($once, ^{
});
- 延时调用(注意仅表示在指定时间后提交任务 ,而非执行任务。如果任务提交到主队列,它将在main runloop中执行,对于每隔1/60秒执行一个的RunLoop,任务最多可能在1+1/60秒后执行)
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
});
- GCD任务组(注意:dispatch_group_notify是异步的,dispatch_group_wait是同步的)
dispatch_queue_t dispatchQueue = dispatch_queue_create("xxx.queue", DISPATCH_QUEUE_CONCURRENT);
dispatch_group_t dispatchGroup = dispatch_group_create();
dispatch_group_async(dispatchGroup, dispatchQueue, ^(){
NSLog(@"任务A");
});
dispatch_group_async(dispatchGroup, dispatchQueue, ^(){
NSLog(@"任务B");
});
dispatch_group_notify(dispatchGroup, dispatch_get_main_queue(), ^(){
NSLog(@"end");
});
GCD的进阶应用
- dispatch_suspend和dispatch_resume
这两个是类似NSOperationQueue的暂停suspend和恢复resume
这些函数不会影响到队列中已经执行的任务,队列暂停后,已经添加到队列中但是还没有执行的任务 不会执行,直到队列被恢复
dispatch_suspend(dispatchQueue);
dispatch_resume(dispatchQueue);
- dispatch_barrier_async
这个是为了解决 在写入数据时,不能再其他线程读取或写入数据。
dispatch_barrier_async会把队列的运行周期分为这三个过程:
1.首先等目前追加到并行队列中所有任务都执行完成
2.开始执行dispatch_barrier_async中的任务这时候即便向并行队列提交任务,也不会执行
3.dispatch_barrier_async中任务执行完成后,并行队列恢复正常。
dispatch_barrier_async起到了承上启下的作用。它保证此前的任务都先于自己执行,此后的任务也迟于自己执行。正如barrier的含义一样,它起到一个栅栏或者分水岭的作用
dispatch_queue_t dispatchQueue = dispatch_queue_create("xxx.queue", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queue, reading1)
dispatch_async(queue, reading2)
//那么可以用dispatch_barrier_async 来进行写入,这样实现承上启下的作用
dispatch_barrier_async(queue, writing)
dispatch_async(queue, reading3)
dispatch_async(queue, reading4)
- dipactch_semaphore
信号量的使用,简单来讲就是 为0 需要等待, 不为0执行任务,并减1
类似于单例的操作,保证线程安全,并且可以通过信号量来控制并发队列执行数量
dispatch_semaphore_t semaphore = dispatch_semaphore_create(5);//初始化信号量为 5 , create()便是初始化信号量,()里是信号量的值,通过初始值来控制任务的并发数量.利用串行队列来保证任务顺序执行,利用并行队列来保证任务是同时进行的
dispatch_queue_t diapatchQueue = dispatch_queue_create("xxx.queue", DISPATCH_QUEUE_CONCURRENT);
dispatch_queue_t serialQueue = dispatch_queue_create("xx.queue",DISPATCH_QUEUE_SERIAL);
for (int i = 0; i < 100000; i++) {
dispatch_async(serialQueue, ^{
/*
某个线程执行到这里,如果信号量为1,那么wait方法返回1,开始执行接下来的操作。与此同时,因为信号量
变为0,其他执行到这里的线程必须等待
*/
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);//这个的作用是一直等待,直到信号量的值大于等于1,当这个方法执行后,会将信号量参数的值减1,减到0的时候,其他任务会进入等待状态
dispatch_async(diapatchQueue, ^{//通过并行队列保证任务同行进行
NSLog(@"thread-info:%@开始执行任务%d",[NSThread currentThread],(int)i);
sleep(1);
NSLog(@"thread-info:%@结束执行任务%d",[NSThread currentThread],(int)i);
/*
操作执行结束,记得要调用signal方法,把信号量的值加1.这样,如果有别的线程在等待wait函数返回,就由最先等待的线程执行
*/
dispatch_semaphore_signal(semaphore);//将信号量的值加1,确保下次可以继续执行
});
});
}
- dispatch_set_target_queue
作用是改变queue的优先级,一般都是把一个任务放到一个串行的queue中,如果这个任务被拆分了,被放置到多个串行的queue中,但实际还是需要这个任务同步执行,那么就会有问题,因为多个串行queue之间是异步进行的。
如果将多个串行的queue使用dispatch_set_target_queue指定到了同一目标,那么着多个串行queue在目标queue上就是同步执行的,不再是异步执行。
参考文章多线程