面试经典-GCD简介

什么是GCD:

    Grand Central Dispath是异步执行任务的技术之一;

    一般将应用程序中记述的线程管理的代码 在系统级中实现;

 

开发者只需要定义想要执行的任务 并追加到Dispath Queue中,GCD就能生成必要的线程并计划执行任务;

 

GCD的线程管理作为系统的一部分实现,提供了系统级的线程管理,比以前的线程效率更高;

 

示例场景:

    对比如下两种实现,使用GCD和performSelector的方式相比,GCD更加简洁明了;

【Code-gcdTmp1】

/*
 * GCD示例场景:在某线程中执行任务 任务执行完成后 z主线程处理结果
 */
-(void)gcdTmp1{
    //执行后台线程
    dispatch_queue_t dispatch = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    dispatch_async(dispatch, ^{
        //执行一段较耗时的操作

        //处理结束后,主线程使用处理结果
        dispatch_async(dispatch_get_main_queue(), ^{
           //只在主线程可以进行的处理(例如用户界面更新)
            
        });
    });
}

【Code-cocoaTmp1】

/*
 * Cocoa框架 NSObject类中提供的简单多线程实例方法performSelectorInBackground和performSelectorOnMainThread
 */
-(void)cocoaTmp2{
    //执行后台线程
    [self performSelectorInBackground:@selector(doSometiong) withObject:nil];
}
-(void)doSometiong{
    @autoreleasepool {
        //执行一段较耗时的操作
        
        //处理结束后,主线程使用处理结果
        [self performSelectorOnMainThread:@selector(doneSometiong) withObject:nil waitUntilDone:NO];
    }
}
-(void)doneSometiong{
    //只在主线程可以进行的处理(例如用户界面更新)
}

 

多线程:

    我们知道Mac产品的操作系统启动应用后,需要将包含应用程序的CPU指令载入内存并执行;

    一个CPU执行的CPU命令列为“一条无分叉路径” 即为线程;

    这样的“无分叉路径”有多条时,即为多线程;

 

现在的CPU芯片都集成了许多个CPU(多核CPU);

 

上下文切换:

    当发生操作系统事件时,如唤起系统调用等情况,内核会切换执行路径;

    执行中路径的状态,如CPU寄存器等信息会保存到各自路径专用的内存块中;

    从切换目标路径专用的内存块中,复原CPU寄存器等信息,继续执行切换路径的CPU命令;

 

并行执行任务:

    使用多线程,程序能在某个线程和其他线程之间反复多次进行上下文切换,这看上去好像是一个CPU核(就是一个CPU)能并列的执行多个任务;但实际上基本1个CPU核一次能执行的CPU命令始终为1(时间片轮转);这只是在一个CPU核上,如果有多个CPU核,就可以真正并行执行多线程了;

 

多线程编程:

    一个线程是一个“无分叉路径”,一个CPU可以通过上下文切换使用多个线程完成任务,但某一时刻实际执行的还是一个线程,而多个CPU就可以并行执行多个线程了;这种利用多线程编程的技术就是 多线程编程;

 

多线程编程的问题:

    数据竞争:多个线程同时更新资源而导致的数据不一致;

    死锁:停止、等待事件的线程会导致多个线程相互持续等待;

    而且,使用太多线程会消耗大量内存;

 

多线程编程最重要的优点:

    保证应用程序的响应性能;

    比如,应用程序启动,最先执行主线程,描绘界面、处理屏幕触摸事件等;

    若,主线程执行了较为耗时的操作,如访问数据库,图像处理,就会阻塞主线程;

    在iOS中,即妨碍主线程中被称为RunLoop的主循环的执行,从而导致界面卡顿;

 

我们这里介绍的GCD 大大简化了较为复杂的多线程编程;

 


 

GCD的使用:

    定义想要执行的任务 并追加到Dispath Queue中,我们一开始已经写过了GCD的示例,通过block定义了想要执行的任务;

    通过dispatch_async函数“追加”赋值在变量queue的Dispath Queue中,这样就可以使指定的Block在另一线程中执行;

 


 

Dispath Queue:

    这是执行处理的等待队列,按照先入先出执行处理;

    执行具体处理的时候,有两种Dispath Queue:

        一种是 等待现在执行中处理的 Serial Dispath Queue;

        另一种是 不等待现在执行中处理的 Concurrent Dispath Queue;

 

Serial Dispath Queue:

    因为需要等待现在执行中的处理结束,所以会依次执行bkl0-6;

    同时执行的处理只有一个,按照顺序执行;

【Code-serialTmp】

/*
 * serial dispatch queue
 */
-(void)serialTmp{
    void (^blk0)(void) = ^{NSLog(@"blk0");};
    void (^blk1)(void) = ^{NSLog(@"blk1");};
    void (^blk2)(void) = ^{NSLog(@"blk2");};
    void (^blk3)(void) = ^{NSLog(@"blk3");};
    void (^blk4)(void) = ^{NSLog(@"blk4");};
    void (^blk5)(void) = ^{NSLog(@"blk5");};
    void (^blk6)(void) = ^{NSLog(@"blk6");};

    dispatch_queue_t dispatch = dispatch_queue_create("com.example.MySerialQueue", NULL);

    dispatch_async(dispatch, blk0);
    dispatch_async(dispatch, blk1);
    dispatch_async(dispatch, blk2);
    dispatch_async(dispatch, blk3);
    dispatch_async(dispatch, blk4);
    dispatch_async(dispatch, blk5);
    dispatch_async(dispatch, blk6);

//    dispatch_release(dispatch);
    
}

Concurrent Dispath Queue:

    不用等待现在执行中的处理结束,所以首先执行bkl0,不管bkl0是否结束,都开始执行后面的bkl1;

    不管bkl1是否结束都开始执行后边的bkl2…,如此进行;

    由于使用了多个线程,这样可以并行的执行多个处理,并行处理的数量取决于当前系统;

【Code-concurrentTmp】


/*
 * Concurrent dispatch queue
 */
-(void)concurrentTmp{
    void (^blk0)(void) = ^{NSLog(@"blk0");};
    void (^blk1)(void) = ^{NSLog(@"blk1");};
    void (^blk2)(void) = ^{NSLog(@"blk2");};
    void (^blk3)(void) = ^{NSLog(@"blk3");};
    void (^blk4)(void) = ^{NSLog(@"blk4");};
    void (^blk5)(void) = ^{NSLog(@"blk5");};
    void (^blk6)(void) = ^{NSLog(@"blk6");};
    
//    dispatch_queue_t dispatch = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    dispatch_queue_t dispatch = dispatch_queue_create("com.example.MyConcurrentQueue", DISPATCH_QUEUE_CONCURRENT);

    dispatch_async(dispatch, blk0);
    dispatch_async(dispatch, blk1);
    dispatch_async(dispatch, blk2);
    dispatch_async(dispatch, blk3);
    dispatch_async(dispatch, blk4);
    dispatch_async(dispatch, blk5);
    dispatch_async(dispatch, blk6);
    
    
//    dispatch_release(dispatch);
    
}

    这里所谓的并行执行 就是使用多个线程同时执行多个处理;并行执行多个处理 使用的线程数由XNU内核决定,只生成所需线程,还会结束不再需要的线程;

 

Concurrent Dispath Queue执行的顺序可能是这样的:

 

线程0线程1线程2线程3
bkl0——1bkl1(执行时间较长)——2bkl2——3bkl3——4
bkl4——5bkl6——7bkl5——6

 


 

dispatch_queue_create:

    这是GCD生成Dispath Queue的API;我们在上边的示例中已经看到了它;

    生成Serial Dispatch Queue:

        dispatch_queue_t dispatch = dispatch_queue_create("com.example.MySerialQueue", NULL);

    生成Concurrent Dispatch Queue:

        dispatch_queue_t dispatch = dispatch_queue_create("com.example.MyConcurrentQueue", DISPATCH_QUEUE_CONCURRENT);

 

Serial Dispatch Queue:

    对于生成的Serial Dispatch Queue虽然受系统资源限制,但理论上可以生成任意多个;

    而且,虽然一个Serial Dispatch Queue中同时只能执行一个追加处理,但是如果将处理分别放入4个Serial Dispatch Queue中,即为同时执行4个处理——即多个Serial Dispatch Queue可并行执行;

    一旦生成Serial Dispatch Queue并追加处理,系统对于一个Serial Dispatch Queue只能生成并使用一个线程(如果有2000个Serial Dispatch Queue,就会生成2000个线程);

 

多线程的问题就是会消耗大量内存,引起上下文切换,大幅降低响应性能;

 

Serial Dispatch Queue的使用场景:

    多个线程更新相同资源导致数据竞争时Serial Dispatch Queue,这样就只有一个线程操作数据,数据就安全了;

    如操作数据库,操作文件等;

 

Concurrent Dispatch Queue:

    介绍完Serial Dispatch Queue,我们知道应该生成仅所需数量的Serial Dispatch Queue;

    当想并行执行且不会发生数据竞争等问题的处理时,使用Concurrent Dispatch Queue;

    对于Concurrent Dispatch Queue来说,由于内核只使用有效管理的线程,因此不会发生Serial Dispatch Queue的生成过多线程的问题;

 

再看dispatch_queue_create函数:

    函数的第一个参数指定Dispatch Queue的名称;

    该命名推荐使用逆序全程域名的方式;且会出现在程序中作为Dispatch Queue的标识;

    第二个参数设置为NULL,如果是生成Concurrent Dispatch Queue,则替换该参数,使用DISPATCH_QUEUE_CONCURRENT;

    该函数的返回值是dispatch_queue_t类型,所有的queue均为此类型变量;

 

关于Dispatch Queue的释放:

    Dispatch Queue必须通过程序员进行释放;

    通过dispatch_queue_create函数生成的Dispatch Queue在使用结束后通过dispatch_release函数释放;

    代码:dispatch_release(dispatch);

 

有了dispatch_release(),相应的就有dispatch_retain()函数:即Dispatch Queue也像OC对象的引用计数内存管理一样;

    需要通过这两个函数进行内存管理;

    相应的通过dispatch_queue_create生成的queue也需要释放;

    queue追加的block会通过dispatch_retain函数持有Dispatch Queue,即便刚追加完Block就dispatch_release()也是可以的,queue会在Block执行完之后释放;

 

在iOS6.0之后的版本,Dispatch Queue的内存管理已经纳入了ARC的管理范畴,此时GCD对象就如同普通的OC对象一样,不再需要dispatch_retain和dispatch_release了;

 


 

Main Dispatch Queue和Global Dispatch Queue:

    对于获取dispatch_queue_t类型的变量的方式,我们已经学了一种:通过dispatch_queue_create函数;

    另外一种就是可以获取系统标准提供的Dispatch Queue;

 

也就是说,即便不用dispatch_queue_create生成,系统也会给我们提供几个Dispatch Queue:

    即Main Dispatch Queue和Global Dispatch Queue;

 

Main Dispatch Queue:

    这是主线程中执行的Dispatch Queue,主线程只有一个,它是一种Serial Dispatch Queue;

    追加到Main Dispatch Queue中的处理 是在主线程的RunLoop中执行的,因此需要将用户更新界面的一些必须在主线程中追加的处理 追加到Main Dispatch Queue中使用;

    这与之前我们介绍的NSObject类的performSelectorOnMainThread方法的执行是一致的;

    通过dispatch_get_main_queue()获取;

 

Global Dispath Queue:

    这是所有应用程序都能够使用的Concurrent Dispath Queue;

    即可以不通过dispatch_queue_create函数生成Concurrent Dispath Queue,直接获取Global Dispath Queue使用即可;

    我们可以通过dispatch_get_global_queue函数获取Global Dispath Queue:

        示例:dispatch_queue_t dispatch = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

        第一个参数表示Global Dispath Queue的执行优先级;

 

Global Dispath Queue的四种执行优先级:

    高优先级:High Priority(参数 DISPATCH_QUEUE_PRIORITY_HIGH)

    默认优先级:Default Priority,(参数DISPATCH_QUEUE_PRIORITY_DEFAULT)

    低优先级:Low Priority,(参数DISPATCH_QUEUE_PRIORITY_LOW)

    后台优先级:Background Priority,(参数DISPATCH_QUEUE_PRIORITY_BACKGROUND)

 

    通过内核管理的用于Global Dispath Queue的线程,将使用相应的Global Dispath Queue优先级作为线程的执行优先级进行使用;追加处理时,需要选用与之对应的执行优先级的Global Dispath Queue;(注意这个优先级只能作程度上的划分,并不具有实时性)

 

相比于生成的Dispath Queue,获取的Dispath Queue要更加容易使用;

 


 

 

 

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值