iOS开发之多线程编程:GCD


1.     什么是GCD?
全称是Grand Central Dispatch,是纯C语言,提供了非常多强大的函数。

2.     GCD的优势
GCD是苹果公司为多核的并行运算提出的解决方案;
GCD会自动利用更多的CPU内核(比如双核、四核);
GCD会自动管理线程的生命周期(创建线程、调度任务、销毁线程);
程序员只需要告诉GCD想要执行什么任务,不需要编写任何线程管理代码;

3.     GCD中有2个核心概念
任务:执行什么操作;
队列:用来存放任务。

4.     队列的类型
并发队列(Concurrent Dispatch Queue)
a. 可以让多个任务并发(同时)执行(自动开启多个线程同时执行任务);
b. 并发功能只有在异步(dispatch_async)函数下才有效。
串行队列(Serial Dispatch Queue)
让任务一个接着一个地执行(一个任务执行完毕后,再执行下一个任务)。
主队列
特殊的串行队列,代表主线程。

5.     GCD执行任务的2个函数
同步的方式执行任务
dispatch_sync(dispatch_queue_t queue, dispatch_block_t block);
异步的方式执行任务
dispatch_async(dispatch_queue_t queue, dispatch_block_t block);
(单词翻译:queue 是队列;block 是任务)

6.     同步和异步的区别
同步:在当前线程中执行。
异步:在另一条线程中执行。

7.     4个术语:同步、异步、并发、串行
同步和异步决定了要不要开启新的线程。
同步:在当前线程中执行任务,不具备开启新线程的能力;
异步:在新的线程中执行任务,具备开启新线程的能力。
并发和串行决定了任务的执行方式。
并发:多个任务并发(同时)执行;
串行:一个任务执行完毕后,再执行下一个任务。

8.     串行队列
概念:
以先进先出的方式,顺序调度队列中的任务执行;无论队列中所指定的执行任务函数是同步还是异步,都会等待前一个任务执行完成后,再调度后面的任务。
创建:
dispatch_queue_create(const char *label,dispatch_queue_attr_t attr);
参数1 Label: 队列名称 
参数2 Attr:  队列的属性 (DISPATCH_QUEUE_SERIAL)
执行:
串行队列,同步行,不开线程 (在当前线程执行)。
dispatch_queue_t q = dispatch_queue_create("test",  DISPATCH_QUEUE_SERIAL);
for (int i = 0; i < 10; i++) {
    //同步执行
    dispatch_sync(q, ^{
       NSLog(@"%@ -- %d",[NSThread currentThread],i);
    });
}
串行队列,异步执行,开一个线程,顺序执行。
只有一个线程,因为是串行队列,只有一个线程就可以按顺序执行队列中的所有任务
dispatch_queue_t q = dispatch_queue_create("test",  DISPATCH_QUEUE_SERIAL);
    for (int i = 0; i < 10; i++) {
        //异步执行
        dispatch_async(q, ^{
            NSLog(@"%@ -- %d",[NSThread currentThread],i);
        });
    }

9.     并发(并行)队列
概念:
以先进先出的方式,并发调度队列中的任务执行。
如果当前调度的任务是 同步执行 的,会等待任务执行完成后,再调度后续的任务;
如果当前调度的任务是 异步执行 的,同时底层线程池有可用的线程资源,会在新的线程调度后续任务的执行。
创建和执行:
并行队列,异步执行。
开多个线程,任务无序执行,效率最大,每次开启多少个线程是不固定的,线程数是由gcd来决定的,不由人为控制。
dispatch_queue_t q = dispatch_queue_create("test",  DISPATCH_QUEUE_CONCURRENT);
    for (int i = 0; i < 10; i++) {
        //异步执行
        dispatch_async(q, ^{
            NSLog(@"%@ -- %d",[NSThread currentThread],i);
        });
    }
并行队列,同步执行。
不开线程,顺序执行 ,与串行队列同步执行一样。

10.     主队列
主队列异步任务:不开线程,同步执行。
主队列特点:如果主线程正在执行代码暂时不调度任务,等主线程执行结束后再执行任务。
主队列又叫 全局串行队列
// 得到主队列
dispatch_queue_t mainQueue = dispatch_get_main_queue();

主队列同步执行:程序执行不出来(死锁)
主队列:如果主线程正在执行代码,就不调度任务。
同步执行:主线程下,往主队列添加任务并同步执行会造成死锁。
//造成死锁的代码
dispatch_sync (mainQueue, ^{
   NSLog ( @"main queue %@" ,[ NSThread currentThread ]);
});

11.  总结各队列的执行效果:

12.     线程间通信:从子线程回到主线程
示例:
dispatch_async(
dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
    // 执行耗时的异步操作...
      dispatch_async(dispatch_get_main_queue(), ^{
        // 回到主线程,执行UI刷新操作
        });
});

13.     同步执行
同步任务,可以让其他异步执行的任务,依赖某一个同步任务。
例如:在用户登录之后,再异步下载文件!

14.     全局并发(并行)队列
概念:
GCD默认已经提供了全局的并发队列,供整个应用使用,不需要手动创建。
定义:
dispatch_queue_t  dispatch_get_global_queue(long identifier, unsigned long flags);
// 参数 1: 优先级 long  identifier qos_class_t(iOS8);在 iOS8 之前参数名为 dispatch_queue_priority_t。
/**
iOS8 之后的优先级 qos_class_t(iOS8)
QOS_CLASS_USER_INTERACTIVE,用户交互(希望最快完成-不能用太耗时的操作)
QOS_CLASS_USER_INITIATED,用户期望(希望快,也不能太耗时)
QOS_CLASS_DEFAULT 0x15, 默认(用来底层重置队列使用的,不是给程序员用的) 
QOS_CLASS_UTILITY,实用工具(专门用来处理耗时操作!)  
QOS_CLASS_BACKGROUND,后台
QOS_CLASS_UNSPECIFIED(0).未指定,可以和iOS 7.0 适配


iOS8 之前的优先级 dispatch_queue_priority_t
#define DISPATCH_QUEUE_PRIORITY_HIGH 2 高
#define DISPATCH_QUEUE_PRIORITY_DEFAULT 0 默认(中)
#define DISPATCH_QUEUE_PRIORITY_LOW (-2) 低
#define DISPATCH_QUEUE_PRIORITY_BACKGROUND INT16_MIN 后台
*/
// 参数 2:flags 没有用 ,是保留参数, 给未来使用 0 即可。
获取:
dispatch_queue_t queue = dispatch_get_global_queue (0, 0);

15.     全局队列 & 并发队列的区别
全局队列:
a. 没有名称;
b. 无论 MRC & ARC 都不需要考虑释放;
c. 日常开发中,建议使用"全局队列”。
并发队列:
a. 有名字,和 NSThread 的 name 属性作用类似;
b. 如果在 MRC 开发时,需要使用 dispatch_release(q); 释放相应的对象;
c. dispatch_barrier 必须使用自定义的并发队列;
d. 开发第三方框架时,建议使用并发队列。
补充:
void  dispatch_barrier_async(dispatch_queue_t queue, dispatch_block_t block);
 函数的作用:
如果任务是通过dispatch_barrier_async函数追加到concurrent queue中的,执行该任务时,其他的线程不执行,直到该任务完成,才恢复执行剩余的任务。

16.     延时执行
方法1:
NSTimer *timer = [NSTimer timerWithTimeInterval:1 target:self selector:@selector(task) userInfo:nil repeats:NO];
方法2:调用NSObject的方法
// 2秒后再调用self的run方法
[self performSelector:@selector(task) withObject:nil afterDelay:1];
方法3: 使用GCD函数
/**
     参数 1: 延时的时间 dispatch_time 生成的时间,以 纳秒为计时单位, 精度高!
     参数 2: 队列
     参数 3: 任务
     异步执行
*/
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        // 1s后异步执行这里的代码。。。
    });

17.     队列组
1.     问题的提出
有这么一种需求:
a:分别异步执行2个耗时的操作;
b:等2个异步操作都执行完毕后,再回到主线程执行操作。
2.     问题的实现
如果想要快速高效地实现上述需求,可以考虑用队列组。
    // 创建组
   
dispatch_group_t group = dispatch_group_create ();
   
// 开启异步任务
   
// 参数 1:
   
// 参数 2: 队列
   
// 参数 3: 任务
   
dispatch_group_async (group, dispatch_get_global_queue (0, 0), ^{
       
// 模拟网络卡
        [
NSThread sleepForTimeInterval : arc4random_uniform (5)];
       
NSLog ( @"LOL1.zip %@" ,[ NSThread currentThread ]);
    });
    dispatch_group_async (group, dispatch_get_global_queue (0, 0), ^{
       
// 模拟网络卡
        [
NSThread sleepForTimeInterval : arc4random_uniform (5)];
       
NSLog ( @"LOL2.zip %@" ,[ NSThread currentThread ]);
    });
    // 参数 1:group
   
// 参数 2: 队列
   
// 参数 3: 任务
   
dispatch_group_notify (group, dispatch_get_main_queue (), ^{
       
NSLog ( @" 下载完成 %@" ,[ NSThread currentThread ]);
    });

18.     队列组扩展
a.  在终端中输入命令  man dispatch_group_async 查看队列组实现的原理。
    dispatch_group_async(dispatch_group_t group, dispatch_queue_t queue, dispatch_block_t block)
    {
       
dispatch_retain (group);
        dispatch_group_enter(group);
        dispatch_async(queue, ^{
            block();
            dispatch_group_leave(group);
           
dispatch_release (group);
        });
    }
b.  重写17.队列组中的Demo如下:
    dispatch_group_t group = dispatch_group_create ();
    dispatch_group_enter (group);
   
dispatch_async ( dispatch_get_global_queue (0, 0), ^{
       
// 模拟网络卡
        [
NSThread sleepForTimeInterval : arc4random_uniform (5)];
       
NSLog ( @"LOL1.zip %@" ,[ NSThread currentThread ]);
       
dispatch_group_leave (group);
    });
    dispatch_group_enter (group);
   
dispatch_async ( dispatch_get_global_queue (0, 0), ^{
       
// 模拟网络卡
        [
NSThread sleepForTimeInterval : arc4random_uniform (5)];
       
NSLog ( @"LOL2.zip %@" ,[ NSThread currentThread ]);
       
dispatch_group_leave (group);
    });
    dispatch_group_notify (group, dispatch_get_main_queue (), ^{
       
NSLog ( @" 下载完成 %@" ,[ NSThread currentThread ]);
    });

19.     单例模式
作用:
可以保证在程序运行过程中,一个类只有一个实例,而且该实例易于供外界访问,从而方便地控制了实例个数,并节约系统资源。
使用场合:
在整个应用程序中,共享一份资源(这份资源只需要创建初始化1次)。
核心代码:只执行一次,线程安全的。
static dispatch_once_t onceToken; 
dispatch_once(&onceToken, ^{
//要初始化的代码
 }); 

20.     两种单例设计模式的比较
a.  创建一个类的两种单例实现方法:
// dispatch_once实现单例
+(instancetype)config{
    static CZConfig *instance;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        instance = [[CZConfig alloc] init];
    });
    return instance;
}
// 同步锁实现单例
+(instancetype)configSync{
    static CZConfig *instance;
    @synchronized(self) {
        if(instance == nil){
            instance = [[CZConfig alloc] init];
        }
    }
    return instance;
}
b.  性能测试与比较:
    //dispatch_once 用时测试
    // CFAbsoluteTimeGetCurrent()主要用于调试函数的耗时,测试代码的效率
    double time1 = CFAbsoluteTimeGetCurrent ();
   
for ( int i = 0; i < 999*999; i++) {
       
dispatch_async ( dispatch_get_global_queue (0, 0), ^{
           
CZConfig *config = [ CZConfig config ];
        });
    }
   
double time2 = CFAbsoluteTimeGetCurrent ();
   
NSLog ( @"dispatch_once-----%f" ,time2-time1);
   
     //  同步锁  用时测试
    double time3 = CFAbsoluteTimeGetCurrent ();
   
for ( int i = 0; i < 999*999; i++) {
       
dispatch_async ( dispatch_get_global_queue (0, 0), ^{
           
CZConfig *config = [ CZConfig configSync ];
        });
    }
   
double time4 = CFAbsoluteTimeGetCurrent ();
   
NSLog ( @"synchronized-----%f" ,time4-time3);
}
打印结果:
2015-10-16 23:00:48.468 16 单例模式 [14005:495101] dispatch_once-----1.231498
2015-10-16 23:00:52.428 16单例模式[14005:495101] synchronized-----3.959540
结论:
dispatch_once 的效率比同步锁 高很多。




你可能感兴趣的:(多线程)