GCD

Posted Kingdev

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了GCD相关的知识,希望对你有一定的参考价值。

什么是GCD

全称是Grand Central Dispatch。GCD 所有的API都在libdispatch.dylib动态库里面Xcode自动导入这个库。

 

GCD的优势

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

2)GCD会自动利用更多的CPU内核;

3)GCD会自动管理线程的声明周期(创建、调度和销毁。方便程序员的使用)

4)GCD 可以通过延迟耗时任务,并让他们在后台运行,从而提高应用的响应速度。

5)对于线程和锁来说,GCD 提供了更简便的方法,从而避免发生并发问题(e.g.使用单例模式,GCD可以用来优化我们的代码。)

 

GCD相关概念

1.任务

执行的操作,GCD遵循:FIFO,先进先出,后进后出,先放进去的任务,先拿出来执行。

2.队列:串行和并行

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

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

串行队列:让任务一个接着一个的执行(必须一个任务结束了再取出下一个任务执行);

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

3.同步和异步

同步和异步决定了能否开启新的线程;

同步:只在当前线程中执行任务,不会重新开启线程(需马上执行);

异步:可以在新的线程中执行任务,具备重新开启线程的能力(不会马上执行,什么时候线程有空再去执行);

 

GCD 函数:

// 1.获取队列

/**
 *  @brief 创建主队列(一种特殊的串行队列)
 *
 *  @return 返回主队列
 */
dispatch_queue_t dispatch_get_main_queue(void)

/**
 *  @brief 创建队列
 *
 *  @param label 队列标签,注意为 const char* 类型
 *  @param attr  队列属性(同步队列:DISPATCH_QUEUE_SERIAL/NULL; 异步队列:DISPATCH_QUEUE_CONCURRENT)
 *
 *  @return 返回创建的同步或异步队列
 */
dispatch_queue_t dispatch_queue_create(const char *label, dispatch_queue_attr_t attr);

/**
 *  @brief 获得全局队列
 *
 *  @param identifier 优先级
                     一般写 0(可以适配 ios7/iOS8)
                     iOS7
                     DISPATCH_QUEUE_PRIORITY_HIGH      2  高优先级
                     DISPATCH_QUEUE_PRIORITY_DEFAULT   0  默认优先级
                     DISPATCH_QUEUE_PRIORITY_LOW     -2  低优先级
                     DISPATCH_QUEUE_PRIORITY_BACKGROUND 后台优先级
                     
                     iOS8
             QQS_CLASS_USER_INITIATED 2 高优先级 QOS_CLASS_DEFAULT     0 默认优先级
             QQS_CLASS_UTILITY    -2 低优先级
             QQS_CLASS_BACKGROUND 后台优先级(最低) * @param flags 保留参数 0 * * @return 返回全局队列
*/ dispatch_queue_t dispatch_get_global_queue(long identifier, unsigned long flags); // 2.GCD中有两个用来执行任务的函数 /** * @brief 同步函数 * * @param queue 队列 * @param block 任务 */ void dispatch_sync(dispatch_queue_t queue, dispatch_block_t block); /** * @brief 异步函数 * * @param queue 队列 * @param block 任务 */ void dispatch_async(dispatch_queue_t queue, dispatch_block_t block);

 

队列和同/异部函数组合:

/**
 全局队列跟并发队列的区别
 1. 全局队列没有名称 并发队列有名称
 2. 全局队列,是供所有的应用程序共享。
 3. 在MRC开发,并发队列,创建完了,需要释放。 全局队列不需要我们管理
 */
#pragma CGD - 全局队列
- (void)GCD_Global_queue {
    // 获得全局队列
    dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
    
    // 添加异步任务
    for (int i = 0; i < 10; i++) {
        dispatch_async(queue, ^{
            NSLog(@"currentThread=%@ i=%d", [NSThread currentThread], i);
        });
    }
}
打印结果:
2016-04-05 08:04:38.004 05-GCD的使用[7353:1070557] currentThread=<NSThread: 0x7f92c8e49e10>{number = 3, name = (null)} i=1
2016-04-05 08:04:38.004 05-GCD的使用[7353:1070556] currentThread=<NSThread: 0x7f92cb006340>{number = 5, name = (null)} i=0
2016-04-05 08:04:38.004 05-GCD的使用[7353:1070440] currentThread=<NSThread: 0x7f92c8e3a540>{number = 4, name = (null)} i=2
2016-04-05 08:04:38.005 05-GCD的使用[7353:1070603] currentThread=<NSThread: 0x7f92c8e03e70>{number = 6, name = (null)} i=3
2016-04-05 08:04:38.005 05-GCD的使用[7353:1070448] currentThread=<NSThread: 0x7f92c8d02b90>{number = 2, name = (null)} i=4
2016-04-05 08:04:38.005 05-GCD的使用[7353:1070557] currentThread=<NSThread: 0x7f92c8e49e10>{number = 3, name = (null)} i=5
分析:全局队列是一种特殊的并发队列。


#pragma GCD- 同步任务的作用
- (void)GCD_Concurrent_Sync {
    // 并发队列
    dispatch_queue_t  queue = dispatch_queue_create("BD", DISPATCH_QUEUE_CONCURRENT);
    
    // 添加任务
    // 同步任务,需要马上执行。 不开新线程
    dispatch_sync(queue, ^{
        NSLog(@"刷新界面 %@", [NSThread currentThread]);
    });
    // 异步任务,不会马上执行。开新线程
    dispatch_async(queue, ^{
        NSLog(@"下载图片AA %@", [NSThread currentThread]);
    });
    dispatch_async(queue, ^{
        NSLog(@"下载图片BB %@", [NSThread currentThread]);
    });
}
打印结果:
2016-04-05 08:06:21.669 05-GCD的使用[7369:1073636] 刷新界面 <NSThread: 0x7f97c0704dc0>{number = 1, name = main}
2016-04-05 08:06:21.670 05-GCD的使用[7369:1074613] 下载图片BB <NSThread: 0x7f97c0403f00>{number = 4, name = (null)}
2016-04-05 08:06:21.670 05-GCD的使用[7369:1074608] 下载图片AA <NSThread: 0x7f97c0615070>{number = 3, name = (null)}



#pragma GCD-主队列
/**
  主队列:专门负责在主线程上调度任务,不会在子线程调度任务,在主队列不允许开新线程.
  同步执行:要马上执行
  结果:死锁
 */
- (void)GCD_Main_queue_sync {
    // 1. 获得主队列
    dispatch_queue_t queue = dispatch_get_main_queue();
    
    NSLog(@"1----");
    
    // 2. 同步执行任务
    for (int i = 0; i < 6; i++) {
        NSLog(@"调度前---");
        // 同步:把任务放到主队列里,但需是马上执行
        dispatch_sync(queue, ^{
            NSLog(@"currentThread=%@ i=%d", [NSThread currentThread], i);
        });
        NSLog(@"Wait one second……");
        [NSThread sleepForTimeInterval:1.0];
    }
    NSLog(@"完成----");
}
打印结果:
2016-04-05 08:00:56.137 05-GCD的使用[7340:1062486] 1----
2016-04-05 08:00:56.138 05-GCD的使用[7340:1062486] 调度前---
分析: 产生了死锁!!同步任务需要马上执行,但是主线程正在执行该函数这个任务,所以需要等该函数执行完,但是该函数也在等这个任务执行完毕,所以造成了死锁!



/**
 主队列:专门负责在主线程上调度任务,不会在子线程调度任务,在主队列不允许开新线程.
 异步执行: 会开新线程,在新线程执行
 结果: 不开线程, 只能在主线程上面,顺序执行!
 */
- (void)GCD_Main_queue_async {
    // 1. 获得主队列
    dispatch_queue_t queue = dispatch_get_main_queue();
    
    NSLog(@"1----");
    
    // 2. 异步执行任务
    for (int i = 0; i < 6; i++) {
        NSLog(@"调度前---");
        // 异步:把任务放到主队列里,但是不需要马上执行
        dispatch_async(queue, ^{
            NSLog(@"currentThread=%@ i=%d", [NSThread currentThread], i);
        });
        NSLog(@"Wait one second……");
        [NSThread sleepForTimeInterval:1.0];
    }
    NSLog(@"完成----");
}
打印结果:
2016-04-05 07:56:43.596 05-GCD的使用[7327:1054178] 1----
2016-04-05 07:56:43.596 05-GCD的使用[7327:1054178] 调度前---
2016-04-05 07:56:43.596 05-GCD的使用[7327:1054178] Wait one second……
2016-04-05 07:56:44.598 05-GCD的使用[7327:1054178] 调度前---
2016-04-05 07:56:44.598 05-GCD的使用[7327:1054178] Wait one second……
2016-04-05 07:56:45.599 05-GCD的使用[7327:1054178] 调度前---
2016-04-05 07:56:45.599 05-GCD的使用[7327:1054178] Wait one second……
2016-04-05 07:56:46.600 05-GCD的使用[7327:1054178] 调度前---
2016-04-05 07:56:46.601 05-GCD的使用[7327:1054178] Wait one second……
2016-04-05 07:56:47.601 05-GCD的使用[7327:1054178] 调度前---
2016-04-05 07:56:47.601 05-GCD的使用[7327:1054178] Wait one second……
2016-04-05 07:56:48.602 05-GCD的使用[7327:1054178] 调度前---
2016-04-05 07:56:48.603 05-GCD的使用[7327:1054178] Wait one second……
2016-04-05 07:56:49.604 05-GCD的使用[7327:1054178] 完成----
2016-04-05 07:56:49.605 05-GCD的使用[7327:1054178] currentThread=<NSThread: 0x7ff208f00ad0>{number = 1, name = main} i=0
2016-04-05 07:56:49.605 05-GCD的使用[7327:1054178] currentThread=<NSThread: 0x7ff208f00ad0>{number = 1, name = main} i=1
2016-04-05 07:56:49.614 05-GCD的使用[7327:1054178] currentThread=<NSThread: 0x7ff208f00ad0>{number = 1, name = main} i=2
2016-04-05 07:56:49.614 05-GCD的使用[7327:1054178] currentThread=<NSThread: 0x7ff208f00ad0>{number = 1, name = main} i=3
2016-04-05 07:56:49.615 05-GCD的使用[7327:1054178] currentThread=<NSThread: 0x7ff208f00ad0>{number = 1, name = main} i=4
2016-04-05 07:56:49.615 05-GCD的使用[7327:1054178] currentThread=<NSThread: 0x7ff208f00ad0>{number = 1, name = main} i=5
分析:异步任务放到队列中,并不会马上执行,而主线程为串行队列,里面的任务(该函数)会马上执行,等待任务(该函数)执行完毕(完成--),再去执行异步任务!!!!!



#pragma GCD测试
/**
  并发队列:可以同时执行多个任务
  同步任务:不会开辟新线程,是在当前线程执行
  结果:不开新线程,顺序一个一个执行。
 */
- (void)GCD_Concurrent_sync {
    NSLog(@"----1");
    //1. 并行队列
    dispatch_queue_t queue = dispatch_queue_create("BD", DISPATCH_QUEUE_CONCURRENT);
    
    // 2. 同步执行任务
    for (int i = 0; i < 10; i++) {
        dispatch_sync(queue, ^{
            NSLog(@"currentThread=%@ i=%d", [NSThread currentThread], i);
        });
    }
    NSLog(@"----2");
}
打印结果:
2016-04-05 08:08:19.435 05-GCD的使用[7399:1078990] ----1
2016-04-05 08:08:19.436 05-GCD的使用[7399:1078990] currentThread=<NSThread: 0x7fa64b506580>{number = 1, name = main} i=0
2016-04-05 08:08:19.436 05-GCD的使用[7399:1078990] currentThread=<NSThread: 0x7fa64b506580>{number = 1, name = main} i=1
2016-04-05 08:08:19.436 05-GCD的使用[7399:1078990] currentThread=<NSThread: 0x7fa64b506580>{number = 1, name = main} i=2
2016-04-05 08:08:19.436 05-GCD的使用[7399:1078990] currentThread=<NSThread: 0x7fa64b506580>{number = 1, name = main} i=3
2016-04-05 08:08:19.436 05-GCD的使用[7399:1078990] currentThread=<NSThread: 0x7fa64b506580>{number = 1, name = main} i=4
2016-04-05 08:08:19.436 05-GCD的使用[7399:1078990] currentThread=<NSThread: 0x7fa64b506580>{number = 1, name = main} i=5
2016-04-05 08:08:19.436 05-GCD的使用[7399:1078990] ----2
分析:同步任务不会开辟新线程,是在当前线程(主线程)执行

/**
 并发队列:可以同时执行多个任务
 异步执行:会开新线程,在新线程执行
 结果:会开很多个线程,同时执行
 */
- (void)GCD_Concurrent_async {
    //1. 并行队列
    dispatch_queue_t queue = dispatch_queue_create("cz", DISPATCH_QUEUE_CONCURRENT);
    
    // 2. 异步执行任务
    for (int i = 0; i < 10; i++) {
        dispatch_async(queue, ^{
            NSLog(@"currentThread=%@ i=%d", [NSThread currentThread], i);
        });
    }
}
打印结果:
2016-04-05 08:09:35.973 05-GCD的使用[7409:1082043] currentThread=<NSThread: 0x7fa190e016e0>{number = 13, name = (null)} i=1
2016-04-05 08:09:35.973 05-GCD的使用[7409:1082042] currentThread=<NSThread: 0x7fa190cbb050>{number = 12, name = (null)} i=0
2016-04-05 08:09:35.973 05-GCD的使用[7409:1082044] currentThread=<NSThread: 0x7fa190d4f080>{number = 14, name = (null)} i=2
2016-04-05 08:09:35.973 05-GCD的使用[7409:1082045] currentThread=<NSThread: 0x7fa190e2f850>{number = 15, name = (null)} i=@"%@", [NSThread currentThread]);
    });
    NSLog(@"完成!!");
}

 

线程间通信(个人觉得就是为了主线程和子线程之间通信设计的):

    // 全局队列,异步函数(开启新的线程)
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        
        NSLog(@"1 = %@", [NSThread currentThread]);
        
        // 耗时任务(下载图片) : 放在全局队列且异步执行
        NSURL *url = [NSURL URLWithString:@"http://pic16.nipic.com/20111009/4632997_120303895251_2.jpg"];
        NSData *data = [NSData dataWithContentsOfURL:url];
        UIImage *image = [UIImage imageWithData:data];
        
        // 回到主线程,刷新UI
        dispatch_async(dispatch_get_main_queue(), ^{
            self.imageView.image = image;
            NSLog(@"2 = %@", [NSThread currentThread]);
        });
    });

 

iOS常用延迟提交:

//// NSThread
/*5];
        NSLog(@"After 5 seconds...");
    });
    
    // 提交第二个block,也是延时5秒打印
    dispatch_async(queue, ^{
        [NSThread sleepForTimeInterval:5];
        NSLog(@"After 5 seconds again...");
    });
    
    // 延时一秒
    NSLog(@"sleep 1 second...");
    [NSThread sleepForTimeInterval:1];
    
    // 挂起队列
    NSLog(@"suspend...");
    dispatch_suspend(queue);
    
    // 延时10秒
    NSLog(@"sleep 10 second...");
    [NSThread sleepForTimeInterval:10];
    
    // 恢复队列
    NSLog(@"resume...");
    dispatch_resume(queue);

打印结果:
2016-04-05 13:18:56.016 08-GCD的其他用法[8053:1408615] sleep 1 second...
2016-04-05 13:18:57.018 08-GCD的其他用法[8053:1408615] suspend...
2016-04-05 13:18:57.019 08-GCD的其他用法[8053:1408615] sleep 10 second...
2016-04-05 13:19:01.018 08-GCD的其他用法[8053:1408652] After 5 seconds...
2016-04-05 13:19:07.020 08-GCD的其他用法[8053:1408615] resume...
2016-04-05 13:19:12.025 08-GCD的其他用法[8053:1408652] After 5 seconds again...
分析 :在dispatch_suspend挂起队列后,第一个block还是在运行且正常输出。
结论 : dispatch_suspend并不会立即暂停正在运行的队列block内的任务,而是在当前block执行完成后,暂停后续的队列block内任务的执行。

 

GCD实现定时器:

    __weak typeof(self) weakSelf = self;
    __block int timeCount = 30; // 实现 30s 倒计时
    // 实现倒计时功能
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
    dispatch_source_set_timer(timer, DISPATCH_TIME_NOW, 1 * NSEC_PER_SEC, 0 * NSEC_PER_SEC);
    dispatch_source_set_event_handler(timer, ^{
        // 触发定时器执行的代码
        if (timeCount <= 0) {
            dispatch_source_cancel(timer);
            dispatch_async(dispatch_get_main_queue(), ^{
                NSLog(@"1 = %@", [NSThread currentThread]);
                NSLog(@"发送验证码");
            });
        } else if (timeCount > 0) {
            dispatch_async(dispatch_get_main_queue(), ^{
                NSLog(@"2 = %@", [NSThread currentThread]);
                NSLog(@"timeCount = %@", [NSString stringWithFormat:@"%d后重新发送", timeCount]);
            });
            timeCount--;
        }
    });
    dispatch_resume(timer);

函数:

/**
 *  @brief 创建一个新的调度源来监视低级别的系统对象和自动提交处理程序块来响应事件调度队列
 *
 *  @param type   调度员支持事件类型
 *  @param handle 句柄,一般传0
 *  @param mask   一般传0
 *  @param queue  队列
 *
 *  @return 返回创建的调度源
 */
dispatch_source_t dispatch_source_create(dispatch_source_type_t type, uintptr_t handle, unsigned long mask, dispatch_queue_t queue);

/**
 *  @brief 为一个定时源设置一个开始时间、事件间隔、误差值
 *
 *  @param source   调度员
 *  @param start    设置开始时间
 *  @param interval 事件的时间间隔
 *  @param leeway   误差值
 */
void dispatch_source_set_timer(dispatch_source_t source, dispatch_time_t start, uint64_t interval, uint64_t leeway);

 

 

建议:

1.慎用 dispatch_sync 同步函数,防止 deaklock(main_queue使用同步队列会导致死锁);

2.滥用dispatch_after做定时器导致crash,最好使用 dispatch_source_set_timer 实现倒计时功能;

 

参考 :

Grand Central Dispatch (GCD) Reference

GCD使用经验与技巧浅谈

iOS详细介绍

以上是关于GCD的主要内容,如果未能解决你的问题,请参考以下文章

说说GCD中的死锁

教你如何取消GCD任务

GCD基本知识

GCD

iOS GCD的使用

ios多线程 -- GCD介绍