GCD

<span style="font-family: 'Helvetica Neue'; background-color: rgb(255, 255, 255);">GCD(Grand Central Dispatch)——iOS 4引入的技术</span>

1.线程的一些基础知识

进程:一个正在运行的程序可以看做一个进程。
线程:程序中独立运行的代码段。

一个进程是由至少一个的线程组成,进程只负责资源的调度和分配,线程才是程序真正的执行单元。
在单核 CPU 时代, CPU只能处理1条线程,只有1条线程在工作
多线程并发执行,其实是CPU快速地在多条线程之间调度(切换)
如果CPU调度线程的时间足够快,就造成了多线程并发执行的假象 来实现软件层面的多线程。创建线程,线程间切换都是有成本开销的。但由于多线程可以避免阻塞所造成的 CPU 计算时间浪费,所以多线程所带来的开销成本总体看来是值得的。任务一般都可以被拆分成多个子任务,如果一个子任务发生了阻塞,计算时间就可以分配给其他子任务。这样就提高了 CPU 的利用率。 而基于XNU内核的iOS在发生操作系统时间时会切换执行路径,将CPU的寄存器等信息保存在专用的内存块中,复原时再从内存块中复原CPU的寄存器等信息信息,继续执行CPU指令。
但是现在的物理CPU芯片一般都是有64个(64核)cpu。 由于硬件上就支持多线程技术,就可以让多个线程真正同时地运行。从而真的提供了多个CPU核并行执行多个线程的技术。

多线程的目的是,通过并发执行提高 CPU 的使用效率,进而提供程序运行效率。

2.iOS中的多线程
而iOS中的多线程API有不少,大体有下面几个
  • NSOperation(基于GCD的OC封装,对于初学者来说接口更友好,性能比GCD差点)
  • NSThread(用的比较少,一般也只会做一些简单的逻辑,)
  • GCD(iOS中多线程的绝对主力,简单方便功能强大,也是这篇文章说的重点)
3.GCD
1.Dispatch Queue (执行处理的等待队列):Dispatch Queue 的执行顺序是FIFO,包含两种
1>Serial  Dispatch Queue -串行(等待现在执行中处理结束)
2>Concurrent Dispatch Queue -并行(不等待现在执行中的处理结束)


4.GCD具体方法

1>创建dispatch_queue_create

dispatch_queue_t OneserialDispatchQueue = dispatch_queue_create(NULL, NULL);
这是创建一个标示为空的串行队列。创建具体的队列和实现场景直接上代码
    //Serial Dispatch Queue的使用场景一般是在处理数据竞争类似的问题
    // Serial Dispatch Queue 虽然是串行队列,但是可以创建多个,然后将执行添加到多个Serial Dispatch Queue中,其执行方式是并发的
    // 第一个参数:队列的标识,---该参数也可以是NULL,但是建议使用程序ID因为在调试和CrashLog中可以方便识别
    //第二个参数:队列的类型(串行/并行) ---可以是NULL那么就是串行 NULL = DISPATCH_QUEUE_SERIAL / DISPATCH_QUEUE_CONCURRENT(并行)
    dispatch_queue_t OneserialDispatchQueue = dispatch_queue_create(NULL, NULL);
    dispatch_queue_t concurrentDispatchQueue = dispatch_queue_create(NULL, DISPATCH_QUEUE_CONCURRENT);
    dispatch_queue_t TwoserialDispatchQueue = dispatch_queue_create("TwoserialDispatchQueue", DISPATCH_QUEUE_SERIAL);
    dispatch_queue_t ThreeserialDispatchQueue = dispatch_queue_create("ThreeserialDispatchQueue", NULL);
    dispatch_async(OneserialDispatchQueue, ^{
        sleep(2);
        NSLog(@"1=");
    });
    dispatch_async(TwoserialDispatchQueue, ^{
        sleep(2);
        NSLog(@"2=");
    });
    dispatch_async(ThreeserialDispatchQueue, ^{
        sleep(2);
        NSLog(@"3=");
    });
   
    
    dispatch_async(OneserialDispatchQueue, ^{
        sleep(2);
        NSLog(@"4=");
    });
    dispatch_async(concurrentDispatchQueue, ^{
        sleep(2);
        NSLog(@"5=");
    });
    NSLog(@"%@",OneserialDispatchQueue);

    // 执行顺序应该是1、2、3、5三个并发先执行(1、2、3、5的执行顺序可能每次都不一样因为是并发,GCD的FIFO指的是同一个线程),4肯定晚于1执行(FIFO原则)。因为1、2、3、5(5是并发队列)是在三个不同的串行队列中然后异步加入队列,4和1是加入到同一个一个串行中,所以必然等到1执行完成后再执行
    
    /*
   在MRC和ARC的iOS6之前(这两种情况现在非常少,我是没见过适配iOS6之前的)我们要对GCD进行内存管理,使用下面的方法。
    
    dispatch_release(OneserialDispatchQueue);
    dispatch_retain(OneserialDispatchQueue);
     
     */
    
    // iOS6之后GCD已经进入ARC的管理了,我们就不用管这个了
2> 获取系统的Dispatch Queue

工作中我们大多数情况下不用自己创建Dispatch Queue,我们可以方便的获取系统标准提供的Dispatch Queue那就是下面的两个

dispatch_queue_t main = dispatch_get_main_queue();

如其名指的就是主线程(更新UI必须在此线程)所以也肯定是 Serial Dispatch Queue它的处理在主线程的RunLoop中执行

还有就是dispatch_get_global_queue

  /*
     dispatch_get_global_queue是 concurrent Dispatch Queue
     第一个参数有四个优先级
     
     #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
     
     再向Global Dispatch Queue 中追加处理时,应选择与处理内容对应的优先级,但是XNU内核用于Global Dispatch Queue的线程并不能保证实时性,只是大致判断
     */
    dispatch_queue_t global = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); // dispatch_queue_create创建的就是DISPATCH_QUEUE_PRIORITY_DEFAULT这种优先级
3>变更优先级:dispatch_set_target_queue

这个一般都用不到

dispatch_queue_t serialDispatchQueue = dispatch_queue_create(NULL, NULL);
    dispatch_queue_t globalDispatchQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0);
    /*
     变更生成的Dispatch Queue的执行优先级
     第一个参数:要变更的Dispatch Queue
     第二个参数;目标Dispatch Queue
     */
    dispatch_set_target_queue(serialDispatchQueue, globalDispatchQueue);

4>延时执行dispatch_after


    // 作用:在指定时间后执行处理
    // 注意:dispatch_after并不是在指定时间后执行处理,而只是在指定时间追加处理到Dispatch Queue,如果在每隔1/60秒执行的RunLoop中,可能会在time+1/60秒后执行甚至更久。
    
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(time * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        NSLog(@"dispatch_after执行");
    });

5>dispatch_group_t
这个比较重要也不是很好理解,就多讲点
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    // 1创建dispatch_group_t
    dispatch_group_t group = dispatch_group_create();
    /* 第一个参数:dispatch_group_t
     第二个参数:要执行的block的Dispatch Queue 加入group的线程可以是任意的,主线程子线程都可以,
    */
    dispatch_group_async(group, queue, ^{
        NSLog(@"任务1");
    });
    dispatch_group_async(group, queue, ^{
        sleep(1);
        NSLog(@"任务2");
    });
    dispatch_group_async(group, queue, ^{
        sleep(2);
        NSLog(@"任务3");
    });
    dispatch_group_async(group, queue, ^{
        NSLog(@"任务4");
    });
    // 3,在所有加到group的任务执行完成后,再执行此任务,无论上边的queue是否一致,
还是上边的Queue和dispatch_group_notify中的Queue是否一致,下边的block总是在上边的执行完成后才执行
    dispatch_group_notify(group, dispatch_get_main_queue(), ^{
        NSLog(@"最后任务");
    });
    NSLog(@"dispatch_group_notify是异步执行");
    // 还有一种用法是将dispatch_group_notify换成dispatch_group_wait,但是dispatch_group_wait会堵塞当前线程,直到等待时间到达或者group执行完成
    dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, 3 * NSEC_PER_SEC);
    // 这个函数是有返回值的
    
    
#warning 最好不要轻易使用dispatch_group_wait,因为会阻塞线程,而且代码也会复杂
   long result = dispatch_group_wait(group, time);
    if (result == 0) {
        NSLog(@"group中的全部处理完成");
        
    }else{
         NSLog(@"group中还有处理在进行");
        
    }
    NSLog(@"dispatch_group_wait阻塞了当前线程");

dispatch_group_t手动管理group关联的block的运行状态(或计数),进入和退出group次数必须匹配

dispatch_group_enter(group);

dispatch_group_leave(group);

dispatch_group_enter(group);

一般这样实现

   dispatch_group_async(group, queue, ^{
      
    });

dispatch_async(queue, ^{

  dispatch_group_leave(group);

});

这两者等价

6>dispatch_barrier_async

dispatch_barrier_async函数会等待追加到Concurrent Dispatch Queue(串行对于此函数是没有意义的)上的并行执行的处理全部结束之后,再执行dispatch_barrier_async中的block,此block执行完成后,Concurrent Dispatch Queue的处理又开始并行执行

此函数在处理数据竞争时是非常有用的,比如读数据时不能写数据,写数据时不能读,读数据可以一块执行

dispatch_barrier_async只能和dispatch_queue_create创建的Queue一起使用,和dispatch_get_global_queue创建的函数一块使用是有问题的.

 dispatch_barrier_async在 一般在自定义并发队列使用

//    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    dispatch_queue_t queue = dispatch_queue_create(NULL, DISPATCH_QUEUE_CONCURRENT);
    dispatch_async(queue, ^{
        sleep(2);
        NSLog(@"读数据1");
    });
    dispatch_async(queue, ^{
        sleep(1);
        NSLog(@"读数据2");
    });
    dispatch_async(queue, ^{
        sleep(3);
        NSLog(@"读数据3");
    });
    
    dispatch_barrier_async(queue, ^{
        NSLog(@"写数据dispatch_barrier_async任务");
    });
    
    dispatch_async(queue, ^{
        sleep(2);
        NSLog(@"读数据4");
    });
    dispatch_async(queue, ^{
        sleep(1);
        NSLog(@"读数据5");
    });




7>dispatch_sync

如其名,就是同步,就是等待当现程处理结束时才执行

我们在主线程需要更新UI,但是在数据没有处理完时不能更新,处理完时要立即更新,所以可以使用此种写法

  NSLog(@"开始");

    dispatch_sync(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
          sleep(1);
        NSLog(@"处理");
    });

    NSLog(@"完结");
#warning dispatch_sync使用时要格外注意,因为很容易造成死锁
    //dispatch_sync,在指定处理没有完成时是不会返回的,下面这种就会造成死锁,原因是主线程在等待block的执行,而无法去执行block
    dispatch_sync(dispatch_get_main_queue(), ^{
        NSLog(@"死锁");
    });
    NSLog(@"永远不会执行");

8>dispatch_suspend

当想要让一个队列的执行暂时挂起可以调用dispatch_suspend,

  dispatch_queue_t queue = dispatch_queue_create("dispatchSuspend", NULL);
// 挂起指定的 Queue
    dispatch_suspend(queue);
// 恢复指定的 Queue
    dispatch_resume(queue);

9>dispatch_semaphore_t信号量

主要也是解决数据竞争等问题

 // 创建semaphore 参数标示计数初始值
    dispatch_semaphore_t semaphore = dispatch_semaphore_create(1);
    // 等待semaphore 当参数计数为0时等待,为1或者大于1时,则减去1而不等待
    dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
    /*
     第二个参数也可以设置成为一段时间,当返回值是0的时候,说明在指定时间内数值达到大于等于1或者semaphore达到大于等于1
     */
 long result = dispatch_semaphore_wait(semaphore, dispatch_time(DISPATCH_TIME_NOW, 1ull * NSEC_PER_SEC));
    if (result == 0) {
        // codeing
    }else{
        // 时间到,semaphore数值还是0
    }
    
    //semaphore 数值加1
    dispatch_semaphore_signal(semaphore);


10>dispatch_suspend队列挂起

  dispatch_queue_t queue = dispatch_queue_create("dispatchSuspend", NULL);
// 挂起指定的 Queue
    dispatch_suspend(queue);
// 恢复指定的 Queue
    dispatch_resume(queue);
11>dispatch_once_t

// 线程安全的只执行一次指定处理,最主要的作用就是创建单例,怎么创建应该做过iOS开发的都知道
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
       // code to be executed once
    });

总结:1.GCD是iOS多线程开发中最常用的技术,功能强大。但是也不要滥用,因为会让你和你的同事在后面的维护上增加难度。所以不要滥用

2.GCD的实现有时候并不能完全按着我们的想法执行,因为苹果会在底层进行自己的优化,比如:

 dispatch_queue_t myQueue = dispatch_queue_create("com.team", NULL);
    dispatch_sync(myQueue, ^{
        NSLog(@"task2 == %@",[NSThread currentThread]);
    });
task2按理应该在 myQueue中执行,但是它实际在main queue中执行。具体原因可以看一下这个 原因。但是这并不影响我们的使用。

3.学好GCD,妈妈再也不担心我的多线程了!!!!!!!!!!!!

文中demo: 点击打开


















  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值