GCD相关知识总结

一:GCD的队列简介

GCD的Queue有三大队列类型:主队列(main)、全局队列(global)、用户队列(create)

(1)主队列:

dispatch_queue_t queue =dispatch_get_main_queue();

获得主线程的dispatch队列,是一个串行队列。无法控制主线程dispatch队列的执行继续或中断。

注意:主队列在使用时不可把dispatch_sync(串行)函数添加到主队列中,否则会造成死锁。例如:

dispatch_sync(dispatch_get_main_queue(), ^{

NSLog(@"blala");

});
关于死锁的知识后面会详细讲解

(2)全局队列:

优先级的对比图
注意第一个参数表示的是优先级
NSQualityOfServiceUserInteractive
与用户交互的任务,这些任务通常跟UI级别的刷新相关,比如动画,这些任务需要在一瞬间完成
NSQualityOfServiceUserInitiated
由用户发起的并且需要立即得到结果的任务,比如滑动scroll view时去加载数据用于后续cell的显示,这些任务通常跟后续的用户交互相关,在几秒或者更短的时间内完成
NSQualityOfServiceUtility
一些可能需要花点时间的任务,这些任务不需要马上返回结果,比如下载的任务,这些任务可能花费几秒或者几分钟的时间
NSQualityOfServiceBackground
这些任务对用户不可见,比如后台进行备份的操作,这些任务可能需要较长的时间,几分钟甚至几个小时
NSQualityOfServiceDefault
优先级介于user-initiated 和 utility,当没有 QoS信息时默认使用,开发者不应该使用这个值来设置自己的任务
,只能是在调用顺序的概率上有大小之分,并不是一定就有先后顺序,结合上面的表来看
例如可以如下设置:
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0);

- (void)GCDDemo2
{
    // 将任务添加到队列
    dispatch_async(dispatch_get_global_queue(QOS_CLASS_USER_INITIATED,0), ^{
        NSLog(@"1号位%@",[NSThread currentThread]);
    });   dispatch_async(dispatch_get_global_queue(QOS_CLASS_USER_INTERACTIVE,0), ^{
  NSLog(@"2号位%@",[NSThread currentThread]);
    });
    
    dispatch_async(dispatch_get_global_queue(QOS_CLASS_DEFAULT,0), ^{
        NSLog(@"3号位%@",[NSThread currentThread]);
    });
  
    dispatch_async(dispatch_get_global_queue(QOS_CLASS_UNSPECIFIED,0), ^{
        NSLog(@"4号位%@",[NSThread currentThread]);
    });

    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        NSLog(@"5号位%@",[NSThread currentThread]);
    });
    
    dispatch_async(dispatch_get_global_queue(QOS_CLASS_BACKGROUND,0), ^{
        NSLog(@"6号位%@",[NSThread currentThread]);
    });
}

其调用之后的打印结果如下

第一次运行时
2016-12-18 21:56:41.920 04-GCD的初体验[6272:323079] 2号位<NSThread: 0x7fae714b5f50>{number = 3, name = (null)}
2016-12-18 21:56:41.919 04-GCD的初体验[6272:323078] 1号位<NSThread: 0x7fae71702d10>{number = 2, name = (null)}
2016-12-18 21:56:41.920 04-GCD的初体验[6272:323081] 3号位<NSThread: 0x7fae7141d5b0>{number = 4, name = (null)}
2016-12-18 21:56:41.920 04-GCD的初体验[6272:323080] 4号位<NSThread: 0x7fae71405750>{number = 5, name = (null)}
2016-12-18 21:56:41.920 04-GCD的初体验[6272:323085] 5号位<NSThread: 0x7fae7150bd60>{number = 6, name = (null)}
2016-12-18 21:56:41.922 04-GCD的初体验[6272:323082] 6号位<NSThread: 0x7fae71704a70>{number = 7, name = (null)}

第二次运行时
2016-12-18 21:58:07.447 04-GCD的初体验[6272:323959] 1号位<NSThread: 0x7fae714c1b10>{number = 13, name = (null)}
2016-12-18 21:58:07.447 04-GCD的初体验[6272:323960] 2号位<NSThread: 0x7fae716041c0>{number = 14, name = (null)}
2016-12-18 21:58:07.447 04-GCD的初体验[6272:323957] 4号位<NSThread: 0x7fae71704a70>{number = 16, name = (null)}
2016-12-18 21:58:07.447 04-GCD的初体验[6272:323961] 3号位<NSThread: 0x7fae714c15d0>{number = 15, name = (null)}
2016-12-18 21:58:07.447 04-GCD的初体验[6272:323959] 5号位<NSThread: 0x7fae714c1b10>{number = 13, name = (null)}
2016-12-18 21:58:07.450 04-GCD的初体验[6272:323962] 6号位<NSThread: 0x7fae716012c0>{number = 17, name = (null)}

(3)用户队列:

dispatch_queue_t queue =dispatch_queue_create("searialQueue",DISPATCH_QUEUE_SERIAL);//串行队列
dispatch_queue_t queue =dispatch_queue_create("concurrentQueue",DISPATCH_QUEUE_CONCURRENT);//并发队列

串行队列是单线程的,不会额外再多开线程

- (void)GCDDemo2
{
    NSLog(@"我点了");
    // 获取主队列
//    dispatch_queue_t queue = dispatch_get_main_queue();
    
//    并发队列的创建
//    dispatch_queue_t queue =dispatch_queue_create("concurrentQueue",DISPATCH_QUEUE_CONCURRENT);
//    串行队列的创建
    dispatch_queue_t queue = dispatch_queue_create("serialQueue", DISPATCH_QUEUE_SERIAL);
    
    for (int i = 0; i < 10; i ++) {
        dispatch_async(queue, ^{
            NSLog(@"1%@",[NSThread currentThread]);
        });
    }
}

打印结果如下

2016-12-18 22:09:14.924 08-主队列和异步同步任务[6408:330652] 我点了
2016-12-18 22:09:14.927 08-主队列和异步同步任务[6408:330682] I = 0,当前线程为 <NSThread: 0x7fe9d1519e70>{number = 2, name = (null)}
2016-12-18 22:09:14.929 08-主队列和异步同步任务[6408:330682] I = 1,当前线程为 <NSThread: 0x7fe9d1519e70>{number = 2, name = (null)}
2016-12-18 22:09:14.929 08-主队列和异步同步任务[6408:330682] I = 2,当前线程为 <NSThread: 0x7fe9d1519e70>{number = 2, name = (null)}
2016-12-18 22:09:14.929 08-主队列和异步同步任务[6408:330682] I = 3,当前线程为 <NSThread: 0x7fe9d1519e70>{number = 2, name = (null)}
2016-12-18 22:09:14.929 08-主队列和异步同步任务[6408:330682] I = 4,当前线程为 <NSThread: 0x7fe9d1519e70>{number = 2, name = (null)}
2016-12-18 22:09:14.930 08-主队列和异步同步任务[6408:330682] I = 5,当前线程为 <NSThread: 0x7fe9d1519e70>{number = 2, name = (null)}
2016-12-18 22:09:14.930 08-主队列和异步同步任务[6408:330682] I = 6,当前线程为 <NSThread: 0x7fe9d1519e70>{number = 2, name = (null)}
2016-12-18 22:09:14.930 08-主队列和异步同步任务[6408:330682] I = 7,当前线程为 <NSThread: 0x7fe9d1519e70>{number = 2, name = (null)}
2016-12-18 22:09:14.958 08-主队列和异步同步任务[6408:330682] I = 8,当前线程为 <NSThread: 0x7fe9d1519e70>{number = 2, name = (null)}
2016-12-18 22:09:14.958 08-主队列和异步同步任务[6408:330682] I = 9,当前线程为 <NSThread: 0x7fe9d1519e70>{number = 2, name = (null)}

并发队列是多线程并行,会开多个线程

- (void)GCDDemo2
{
    NSLog(@"我点了");
    //    获取主队列
    //    dispatch_queue_t queue = dispatch_get_main_queue();
    //    串行队列的创建
    //    dispatch_queue_t queue = dispatch_queue_create("serialQueue", DISPATCH_QUEUE_SERIAL);
    //    并发队列的创建
    dispatch_queue_t queue =dispatch_queue_create("concurrentQueue",DISPATCH_QUEUE_CONCURRENT);
    for (int i = 0; i < 10; i ++) {
        dispatch_async(queue, ^{
            NSLog(@"I = %d,当前线程为 %@",i,[NSThread currentThread]);
        });
    }
}

打印结果如下

2016-12-18 22:12:35.024 08-主队列和异步同步任务[6425:332369] 我点了
2016-12-18 22:12:35.025 08-主队列和异步同步任务[6425:332755] I = 0,当前线程为 <NSThread: 0x7fcbc3707d80>{number = 2, name = (null)}
2016-12-18 22:12:35.026 08-主队列和异步同步任务[6425:332762] I = 2,当前线程为 <NSThread: 0x7fcbc352acf0>{number = 4, name = (null)}
2016-12-18 22:12:35.026 08-主队列和异步同步任务[6425:332760] I = 1,当前线程为 <NSThread: 0x7fcbc370b880>{number = 3, name = (null)}
2016-12-18 22:12:35.027 08-主队列和异步同步任务[6425:332755] I = 3,当前线程为 <NSThread: 0x7fcbc3707d80>{number = 2, name = (null)}
2016-12-18 22:12:35.028 08-主队列和异步同步任务[6425:332763] I = 4,当前线程为 <NSThread: 0x7fcbc36080e0>{number = 5, name = (null)}
2016-12-18 22:12:35.028 08-主队列和异步同步任务[6425:332762] I = 5,当前线程为 <NSThread: 0x7fcbc352acf0>{number = 4, name = (null)}
2016-12-18 22:12:35.029 08-主队列和异步同步任务[6425:332766] I = 6,当前线程为 <NSThread: 0x7fcbc3605430>{number = 6, name = (null)}
2016-12-18 22:12:35.029 08-主队列和异步同步任务[6425:332760] I = 7,当前线程为 <NSThread: 0x7fcbc370b880>{number = 3, name = (null)}
2016-12-18 22:12:35.029 08-主队列和异步同步任务[6425:332767] I = 8,当前线程为 <NSThread: 0x7fcbc3709a60>{number = 7, name = (null)}
2016-12-18 22:12:35.029 08-主队列和异步同步任务[6425:332755] I = 9,当前线程为 <NSThread: 0x7fcbc3707d80>{number = 2, name = (null)}

二:队列优先级的设定

dispatch_queue_create创建队列的优先级跟global dispatch queue的默认优先级一样,假如我们需要设置队列的优先级,可以通过dispatch_queue_attr_make_with_qos_class或者dispatch_set_target_queue方法;

//指定队列的QoS类别为QOS_CLASS_UTILITY
dispatch_queue_attr_t queue_attr = dispatch_queue_attr_make_with_qos_class (DISPATCH_QUEUE_SERIAL, QOS_CLASS_UTILITY,-1);
dispatch_queue_t queue = dispatch_queue_create("queue", queue_attr);

dispatch_set_target_queue的第一个参数为要设置优先级的queue,第二个参数是对应的优先级参照物

dispatch_queue_t serialQueue = dispatch_queue_create("com.example.MyQueue",NULL);  
dispatch_queue_t globalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND,0);  
//serialQueue现在的优先级跟globalQueue的优先级一样
dispatch_set_target_queue(serialQueue, globalQueue);

dispatch_set_target_queue的使用

dispatch_set_target_queue除了能用来设置队列的优先级之外,还能够创建队列的层次体系,当我们想让不同队列中的任务同步的执行时,我们可以创建一个串行队列,然后将这些队列的target指向新创建的队列即可,比如

队列体系
    
    dispatch_queue_t targetQueue = dispatch_queue_create("target_queue", DISPATCH_QUEUE_SERIAL);
    dispatch_queue_t queue1 = dispatch_queue_create("queue1", DISPATCH_QUEUE_SERIAL);
    dispatch_queue_t queue2 = dispatch_queue_create("queue2", DISPATCH_QUEUE_CONCURRENT);
    dispatch_set_target_queue(queue1, targetQueue);
    dispatch_set_target_queue(queue2, targetQueue);
    dispatch_async(queue1, ^{
        NSLog(@"do job1,%@",[NSThread currentThread]);
        [NSThread sleepForTimeInterval:3.f];
    });
    dispatch_async(queue2, ^{
        NSLog(@"do job2 %@",[NSThread currentThread]);
        [NSThread sleepForTimeInterval:2.f];
    });
    dispatch_async(queue2, ^{
        NSLog(@"do job3 %@",[NSThread currentThread]);
        [NSThread sleepForTimeInterval:1.f];
    });

可以看到执行的结果如下,这些队列会同步的执行任务

2016-12-19 00:08:29.372 09-全局并发队列[7035:379438] do job1,<NSThread: 0x7f7fcb7181c0>{number = 2, name = (null)}
2016-12-19 00:08:32.374 09-全局并发队列[7035:379438] do job2 <NSThread: 0x7f7fcb7181c0>{number = 2, name = (null)}
2016-12-19 00:08:34.380 09-全局并发队列[7035:379438] do job3 <NSThread: 0x7f7fcb7181c0>{number = 2, name = (null)}

不使用dispatch_set_target_queue,即注释掉前面设置的依赖关系之后,我们看运行结果

   dispatch_queue_t targetQueue = dispatch_queue_create("target_queue", DISPATCH_QUEUE_SERIAL);
    dispatch_queue_t queue1 = dispatch_queue_create("queue1", DISPATCH_QUEUE_SERIAL);
    dispatch_queue_t queue2 = dispatch_queue_create("queue2", DISPATCH_QUEUE_CONCURRENT);
   // dispatch_set_target_queue(queue1, targetQueue);
   // dispatch_set_target_queue(queue2, targetQueue);
    dispatch_async(queue1, ^{
        NSLog(@"do job1,%@",[NSThread currentThread]);
        [NSThread sleepForTimeInterval:3.f];
    });
    dispatch_async(queue2, ^{
        NSLog(@"do job2 %@",[NSThread currentThread]);
        [NSThread sleepForTimeInterval:2.f];
    });
    dispatch_async(queue2, ^{
        NSLog(@"do job3 %@",[NSThread currentThread]);
        [NSThread sleepForTimeInterval:1.f];
    });
    

运行结果如下:

2017-02-10 15:40:12.964 GCDDemo[1233:70659] do job2 <NSThread: 0x60800006dd80>
{number = 4, name = (null)}
2017-02-10 15:40:12.964 GCDDemo[1233:70661] do job3 <NSThread: 0x60000006edc0>
{number = 5, name = (null)}
2017-02-10 15:40:12.964 GCDDemo[1233:70658] do job1,<NSThread: 0x60800006dd40>
{number = 3, name = (null)}

dispatch_barrier_async的使用

dispatch_barrier_async用于等待前面的任务执行完毕后自己才执行,而它后面的任务需等待它完成之后才执行。一个典型的例子就是数据的读写,通常为了防止文件读写导致冲突,我们会创建一个串行的队列,所有的文件操作都是通过这个队列来执行,比如FMDB,这样就可以避免读写冲突。不过其实这样效率是有提升的空间的,当没有更新数据时,读操作其实是可以并行进行的,而写操作需要串行的执行,如何实现呢:

dispatch_queue_t queue = dispatch_queue_create("Database_Queue", DISPATCH_QUEUE_CONCURRENT);
    
    dispatch_async(queue, ^{
        NSLog(@"reading data1,currentThread = %@",[NSThread currentThread]);
    });
    dispatch_async(queue, ^{
        NSLog(@"reading data2,currentThread = %@",[NSThread currentThread]);
    });
    dispatch_async(queue, ^{
        NSLog(@"reading data3,currentThread = %@",[NSThread currentThread]);
    });
    dispatch_barrier_async(queue, ^{
        NSLog(@"writing data1,currentThread = %@",[NSThread currentThread]);
        [NSThread sleepForTimeInterval:1];
        
    });
    
    dispatch_barrier_async(queue, ^{
        NSLog(@"writing data2,currentThread = %@",[NSThread currentThread]);
        [NSThread sleepForTimeInterval:1];
        
    });
    dispatch_async(queue, ^{
        [NSThread sleepForTimeInterval:1];
        NSLog(@"reading data4,currentThread = %@",[NSThread currentThread]);
    });
    dispatch_barrier_async(queue, ^{
        NSLog(@"writing data3,currentThread = %@",[NSThread currentThread]);
        [NSThread sleepForTimeInterval:1];
        
    });
    dispatch_async(queue, ^{
        [NSThread sleepForTimeInterval:1];
        NSLog(@"reading data5,currentThread = %@",[NSThread currentThread]);
    });

执行结果如下

GCDDemo[1310:77528] reading data2,currentThread = <NSThread: 0x60800007ee40>{number = 4, name = (null)}
GCDDemo[1310:77531] reading data3,currentThread = <NSThread: 0x60000007a240>{number = 5, name = (null)}
GCDDemo[1310:77549] reading data1,currentThread = <NSThread: 0x60000007a500>{number = 3, name = (null)}
GCDDemo[1310:77549] writing data1,currentThread = <NSThread: 0x60000007a500>{number = 3, name = (null)}
GCDDemo[1310:77549] writing data2,currentThread = <NSThread: 0x60000007a500>{number = 3, name = (null)}
GCDDemo[1310:77549] reading data4,currentThread = <NSThread: 0x60000007a500>{number = 3, name = (null)}
GCDDemo[1310:77549] writing data3,currentThread = <NSThread: 0x60000007a500>{number = 3, name = (null)}
GCDDemo[1310:77549] reading data5,currentThread = <NSThread: 0x60000007a500>{number = 3, name = (null)}

我们将写数据的操作放在dispatch_barrier_async中,这样能确保在写数据的时候会等待前面的读操作完成,而后续的读操作也会等到写操作完成后才能继续执行,提高文件读写的执行效率
FMDB 中如何防止死锁之
dispatch_queue_set_specific 、dispatch_get_specific方法的使用

dispatch_queue_t queue = dispatch_queue_create("myqueue", DISPATCH_QUEUE_CONCURRENT);
//并发的运行一个block任务5次
    dispatch_queue_t queue = dispatch_queue_create("myqueue", DISPATCH_QUEUE_CONCURRENT);
    //并发的运行一个block任务5次
    dispatch_apply(5, queue, ^(size_t i) {
        NSLog(@"do a job %zu times,currentThread = %@",i+1,[NSThread currentThread]);
    });
    NSLog(@"go on");

打印结果如下:

GCDDemo[1361:82100] do a job 1 times,currentThread = <NSThread: 0x60800007fc00>{number = 1, name = main}
GCDDemo[1361:82155] do a job 2 times,currentThread = <NSThread: 0x60800026d280>{number = 3, name = (null)}
GCDDemo[1361:82178] do a job 3 times,currentThread = <NSThread: 0x60800026ce40>{number = 4, name = (null)}
GCDDemo[1361:82100] do a job 5 times,currentThread = <NSThread: 0x60800007fc00>{number = 1, name = main}
GCDDemo[1361:82153] do a job 4 times,currentThread = <NSThread: 0x60800026c2c0>{number = 5, name = (null)}
GCDDemo[1361:82100] go on

在某些场景下使用dispatch_apply会对性能有很大的提升,比如你的代码需要以每个像素为基准来处理计算image图片。同时dispatch apply能够避免一些线程爆炸的情况发生(创建很多线程)

//危险,可能导致线程爆炸以及死锁,开的线程过多,耗时长,因为GCD没有进行统一管理,
for (int i = 0; i < 999; i++){
   dispatch_async(q, ^{...});
}
dispatch_barrier_sync(q, ^{});

// 较优选择, GCD 会管理并发,开的线程是有限的,耗时短,内存消耗少
dispatch_apply(999, q, ^(size_t i){...});

不使用dispatch_apply具体实例演示如下:


    dispatch_queue_t queue = dispatch_queue_create("myqueue", DISPATCH_QUEUE_CONCURRENT);
      NSLog(@"begin");
    
    NSDate *date1 = [NSDate date];
    //危险,可能导致线程爆炸以及死锁,开的线程过多,耗时长,因为GCD没有进行统一管理,
    for (int i = 0; i < 999; i++){
        dispatch_async(queue, ^{
            NSLog(@"do a job %d times,currentThread = %@",i,[NSThread currentThread]);
        });
    }
    dispatch_barrier_sync(queue, ^{
        NSDate *date2 = [NSDate date];
        double time = [date2 timeIntervalSinceDate:date1];
        NSLog(@"end,用时time= %f",time);
    });

打印结果入下:

开启的线程总数多大67个,内存消耗23M
2017-02-10 16:21:25.038 GCDDemo[1454:92682] end,用时time= 0.418642

使用dispatch_apply具体实例演示如下:

   dispatch_queue_t queue = dispatch_queue_create("myqueue", DISPATCH_QUEUE_CONCURRENT);
      NSLog(@"begin");
    
    NSDate *date1 = [NSDate date];
    
//    // 较优选择, GCD 会管理并发,开的线程是有限的,耗时短,内存消耗少
    dispatch_apply(999, queue, ^(size_t i){
    NSLog(@"do a job %zu times,currentThread = %@",i,[NSThread currentThread]);

});

     NSDate *date2 = [NSDate date];
        double time = [date2 timeIntervalSinceDate:date1];
        NSLog(@"end,用时time= %f",time);
    dispatch_barrier_sync(queue, ^{
   
    });

打印结果如下:

开启的线程总数5个,内存消耗18M
2017-02-10 16:30:18.756 GCDDemo[1506:98719] end,用时time= 0.620222

Dispatch Block:

添加到gcd队列中执行的任务是以block的形式添加的,block封装了需要执行功能,block带来的开发效率提升就不说了,gcd跟block可以说是一对好基友,能够很好的配合使用。

创建block
我们可以自己创建block并添加到queue中去执行

dispatch_queue_t queue = dispatch_queue_create("queue", DISPATCH_QUEUE_SERIAL);
//创建block
dispatch_block_t block = dispatch_block_create(0, ^{
        NSLog(@"do something");
    });
dispatch_async(queue, block);

在创建block的时候我们也可以通过设置QoS,指定block对应的优先级,在dispatch_block_create_with_qos_class中指定QoS类别即可:

注意此处不要混淆,我们其实也可以对queue设置优先级,不过要使用这个方法,
//指定队列的QoS类别为QOS_CLASS_UTILITY
//dispatch_queue_attr_t queue_attr = dispatch_queue_attr_make_with_qos_class //(DISPATCH_QUEUE_SERIAL, QOS_CLASS_UTILITY,-1);
//dispatch_queue_t queue = dispatch_queue_create("queue", queue_attr);


dispatch_queue_t queue = dispatch_queue_create("queue", DISPATCH_QUEUE_SERIAL);
dispatch_block_t block = dispatch_block_create_with_qos_class(0, QOS_CLASS_USER_INITIATED, -1, ^{
        NSLog(@"do something with QoS");
    });
dispatch_async(queue, block);

dispatch_block_wait接口的调动用

当需要等待前面的任务执行完毕时,我们可以使用dispatch_block_wait这个接口,设置等待时间DISPATCH_TIME_FOREVER会一直等待直到前面的任务完成:

dispatch_queue_t queue = dispatch_queue_create("queue", DISPATCH_QUEUE_SERIAL);
dispatch_block_t block = dispatch_block_create(0, ^{
    NSLog(@"before sleep");
    [NSThread sleepForTimeInterval:1];
    NSLog(@"after sleep");
});
dispatch_async(queue, block);
//等待前面的任务执行完毕
dispatch_block_wait(block, DISPATCH_TIME_FOREVER);
NSLog(@"coutinue");

打印结果如下

2017-02-11 09:56:04.714 GCDDemo[3402:261809] before sleep
2017-02-11 09:56:04.714 GCDDemo[3402:261473] coutinue
2017-02-11 09:56:14.715 GCDDemo[3402:261809] after sleep
- (void)waiteBlockDemo{
     dispatch_queue_t queue = dispatch_queue_create("queue", DISPATCH_QUEUE_SERIAL);
    dispatch_block_t block = dispatch_block_create(0, ^{
        NSLog(@"before sleep");
        [NSThread sleepForTimeInterval:10];
        NSLog(@"after sleep");
    });
    dispatch_async(queue, block);
    //只设置等待前面的任务执行一秒钟,一秒内要是前面的block执行不完的话就会跳过去直接执行后面
    
    dispatch_time_t time = 1.0;
    dispatch_block_wait(block, time);
    NSLog(@"coutinue");
}

打印结果如下

2017-02-11 09:56:45.059 GCDDemo[3416:262712] before sleep
2017-02-11 09:56:45.059 GCDDemo[3416:262642] coutinue
2017-02-11 09:56:55.061 GCDDemo[3416:262712] after sleep

dispatch_block_notify(针对block任务在没有任何关系的queue中建立依赖关系的情况)

dispatch_block_notify当观察的某个block执行结束之后立刻通知提交另一特定的block到指定的queue中执行

//该函数有三个参数,第一参数是需要观察的block,第二个参数是被通知block提交执行的queue,第三参数是当需要被通知执行的block,函数的原型:
void dispatch_block_notify(dispatch_block_t block, dispatch_queue_t queue,
      dispatch_block_t notification_block);

具体的使用方法如下:

dispatch_queue_t queue = dispatch_queue_create("queue", DISPATCH_QUEUE_SERIAL);
  dispatch_block_t previousBlock = dispatch_block_create(0, ^{
      NSLog(@"previousBlock begin");
      [NSThread sleepForTimeInterval:1];
      NSLog(@"previousBlock done");
  });
  dispatch_async(queue, previousBlock);
  dispatch_block_t notifyBlock = dispatch_block_create(0, ^{
      NSLog(@"notifyBlock");
  });
  //当previousBlock执行完毕后,提交notifyBlock到global queue中执行
  dispatch_block_notify(previousBlock, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), notifyBlock);

运行结果如下:

GCDTests[17129:895673] previousBlock begin
GCDTests[17129:895673] previousBlock done
GCDTests[17129:895673] notifyBlock

dispatch_block_cancel

之前在介绍nsopreration的时候提到它的一个优点是可以取消某个operation,现在在iOS8之后,提交到gcd队列中的dispatch block也可取消了,只需要简单的调用dispatch_block_cancel传入想要取消的block即可:

dispatch_queue_t queue = dispatch_queue_create("queue", DISPATCH_QUEUE_SERIAL);
dispatch_block_t block1 = dispatch_block_create(0, ^{
    NSLog(@"block1 begin");
    [NSThread sleepForTimeInterval:1];
    NSLog(@"block1 done");
});
dispatch_block_t block2 = dispatch_block_create(0, ^{
    NSLog(@"block2 ");
});
dispatch_block_cancel(block2);
dispatch_async(queue, block1);
dispatch_async(queue, block2);

运行结果如下,block2不会再执行了,** (注意顺序一定要放对,将cancel放在async之前)**

2016-12-19 15:48:54.169 09-全局并发队列[9043:566646] block1 begin
2016-12-19 15:48:54.721 09-全局并发队列[9043:566646] block1 done

Dispatch Group

当我们想在gcd queue中所有的任务执行完毕之后做些特定事情的时候,也就是队列的同步问题,如果队列是串行的话,那将该操作最后添加到队列中即可,但如果队列是并行队列的话,这时候就可以利用dispatch_group来实现了,dispatch_group能很方便的解决同步的问题。dispatch_group_create可以创建一个group对象,然后可以添加block到该组里面,下面看下它的一些用法:

dispatch_group_wait
dispatch_group_wait会同步地等待group中所有的block执行完毕后才继续执行,类似于dispatch barrier

dispatch_queue_t queue = dispatch_queue_create("queue", DISPATCH_QUEUE_CONCURRENT);
dispatch_group_t group = dispatch_group_create();
//将任务异步地添加到group中去执行
dispatch_group_async(group,queue,^{ NSLog(@"block1"); });
dispatch_group_async(group,queue,^{ NSLog(@"block2"); });
dispatch_group_wait(group,DISPATCH_TIME_FOREVER);
NSLog(@"go on");

执行结果如下,只有block1跟block2执行完毕后才会执行dispatch_group_wait后面的内容

GCDTests[954:41031] block2
GCDTests[954:41032] block1
GCDTests[954:40847] go on

dispatch_group_notify

功能与dispatch_group_wait类似,不过该过程是异步的,不会阻塞该线程,dispatch_group_notify有三个参数

void dispatch_group_notify(dispatch_group_t group, //要观察的group
                         dispatch_queue_t queue,   //block执行的队列
                         dispatch_block_t block);   //当group中所有任务执行完毕之后要执行的block

简单使用如下:

- (void)BlockDemo{
    dispatch_queue_t queue = dispatch_queue_create("queue", DISPATCH_QUEUE_CONCURRENT);
    dispatch_group_t group = dispatch_group_create();
    dispatch_group_async(group,queue,^{ NSLog(@"block1,%@",[NSThread currentThread]); });
    dispatch_group_async(group,queue,^{ NSLog(@"block2,%@",[NSThread currentThread]); });
    dispatch_group_notify(group, dispatch_get_main_queue(), ^{
        NSLog(@"done,%@",[NSThread currentThread]);
    });
    NSLog(@"go on");

}

打印结果如下:

2016-12-19 16:02:35.321 09-全局并发队列[9111:575287] go on
2016-12-19 16:02:35.321 09-全局并发队列[9111:575419] block1,<NSThread: 0x7ffbe2c08c10>{number = 2, name = (null)}
2016-12-19 16:02:35.321 09-全局并发队列[9111:575430] block2,<NSThread: 0x7ffbe2e01a00>{number = 3, name = (null)}
2016-12-19 16:02:35.322 09-全局并发队列[9111:575287] done,<NSThread: 0x7ffbe2d07360>{number = 1, name = main}

三:关于死锁的相关介绍

GCD避免死锁的三要素
平时总在用GCD,但你知不知道,GCD一不小心就会出现死锁,如果死锁在主线程上,整个程序就完了,所以避免死锁是我们则无旁贷的责任。

那我们先看看造成GCD死锁的三要素(以下内容是我个人总结,并写了一些测试用例,如有不对的地方,请大家指正 PS:我把GCD死锁分成 “普通死锁” ,“高级死锁”,"混合死锁")

普通死锁 必要条件

1.队列 串行队列:1 并行队列:0
2.调度方法 同步调用(dispatch_sync):1 异步调用(dispatch_async):0
3.同一个Q 当前所分发到的Q 和 "外部Q" 是否是同一个Q 同一个:1 不同:0

PS:"外部Q"是指
1.当前嵌套在外部的Q 如以下形式,Q1就是外部Q

dispatch_sync(Q1, ^{
     NSLog(@"b");
     dispatch_sync(Q2, ^{
            NSLog(@"c");
     });
  });)

2.外部的方法执行时 所在的Q 如以下形式 dead1第二次运行时所在的Q是Q1 它就是外部Q

- (void)dead1{
    NSLog(@"不死1");
     __weak typeof (self) weakSelf = self;
     dispatch_sync(Q1, ^{
           NSLog(@"第二次运行死");
           [weakSelf dead1];
});}

结论: 如果三个条件同时满足 则死锁,如果其中一个不满足 不会死锁

高级死锁 必要条件

1.调度方法 同步阻塞调用(dispatch_barrier_sync):1 异步阻塞调用(dispatch_barrier_async):0
2.同一个Q 当前所分发到的Q 和 "外部Q" 是否是同一个Q 同一个:1 不同:0
结论: 如果两个条件同时满足 则死锁,如果其中一个不满足 不会死锁 (高级死锁主要针对dispatch_barrier而言)
实例如下:

- (void)dispatchDemo3{
// 这里和使用什么queue没有关系
    dispatch_queue_t q = dispatch_queue_create("myqueue", DISPATCH_QUEUE_CONCURRENT);
    dispatch_async(q, ^{
//        危险,可能导致线程爆炸以及死锁
        for (int i = 0; i < 3; i++){
            dispatch_async(q, ^{
                NSLog(@"当前线程为%@,结果为%d",[NSThread currentThread],i);
            });
        }
//        关键是这里要是用dispatch_barrier_sync就肯定会发生死锁
        dispatch_barrier_async(q, ^{
            NSLog(@"么有死锁发生");
        }); 
    });
}

打印结果如下:

2016-12-19 16:32:59.255 09-全局并发队列[9234:589490] 当前线程为<NSThread: 0x7fe509c04fb0>{number = 2, name = (null)},结果为0
2016-12-19 16:32:59.255 09-全局并发队列[9234:589483] 当前线程为<NSThread: 0x7fe509d24e50>{number = 3, name = (null)},结果为1
2016-12-19 16:32:59.255 09-全局并发队列[9234:589494] 当前线程为<NSThread: 0x7fe509d0fd20>{number = 4, name = (null)},结果为2
2016-12-19 16:32:59.257 09-全局并发队列[9234:589494] 么有死锁发生

混合死锁 必要条件

1.调度方法 同步阻塞调用(dispatch_barrier_sync)或同步调用(dispatch_sync):1 异步阻塞调用(dispatch_barrier_async)或异步调用(dispatch_async):0
2.同一个Q 当前所分发到的Q 和 "外部Q" 是否是同一个Q 同一个:1 不同:0

结论: 如果两个条件同时满足 则死锁,如果其中一个不满足 不会死锁 (混合死锁主要针对dispatch_barrier 和 dispatch_async,dispatch_sync嵌套调用而言)

以下是死锁测试用例如下:

//混合模式,内部使用dispatch_sync 又是同一Q _concurrentQueue 死锁
- (void)dispatchDemo3{
// 这里和使用什么queue没有关系
    dispatch_queue_t q = dispatch_queue_create("myqueue", DISPATCH_QUEUE_CONCURRENT);
    //混合模式,内部使用dispatch_sync 又是同一Q _concurrentQueue 死锁
        NSLog(@"不死1");
        dispatch_barrier_async(q, ^{
            NSLog(@"不死2");
            dispatch_sync(q, ^{
                NSLog(@"死在子线程上");
            });
        });
    }

打印结果如下:

2016-12-19 16:44:43.074 09-全局并发队列[9274:594740] 不死1
2016-12-19 16:44:43.075 09-全局并发队列[9274:594781] 不死2
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,222评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,455评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,720评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,568评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,696评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,879评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,028评论 3 409
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,773评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,220评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,550评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,697评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,360评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,002评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,782评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,010评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,433评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,587评论 2 350

推荐阅读更多精彩内容