GCD

作者: 玄裳 | 来源:发表于2016-12-13 14:56 被阅读0次

说到CGD,我们先来了解一下容易混淆的概念:

基本概念.png

最纠结的问题之一:什么是并行

多个线程可以同时执行多个任务的处理便为“并行执行”。

并行是并发的一种特殊情况,并行的前提是并发,并发不一定是并行。
串行队列和并行队列都有“并行执行”的情况。如果多个串行队列在各自线程同时各执行任务也是并行执行(其实也是多线程并发执行任务)。

关于串行并发并行借自己的生活场景以及自己的理解来打个比方:
家里有两个煤气灶台,一个电磁炉。
串行:电磁炉做菜,我做完一个菜之后才可以做第二个菜。
并发:煤气灶做菜,我不用管第一个灶台做没做完了,就打开第二个煤气灶做菜了(好比开启新线程)。
并行:两个煤气灶同时在做菜。当然电磁炉也可以出现并行的情况,再买个电磁炉,2个电磁炉单独做菜也可以达到并行的效果了。(不过酱紫十分消耗资源阿)

GCD队列类型的区别

GCD队列类型的区别.png

Dispatch Queue

Dispatch Queue是执行处理的等待队列。遵循先进先出(FIFO,first-in-first-out)的原则。

Dispatch Queue的执行任务方式有两种:

Dispatch Queue的种类.png

dispatch_async中追加处理时,源代码输出两种Queue的区别:

   dispatch_async(queue,blk0);
   dispatch_async(queue,blk1);
   dispatch_async(queue,blk2);
   dispatch_async(queue,blk3);
   dispatch_async(queue,blk4);
   dispatch_async(queue,blk5);

当queue是Serial Dispatch Queue时,执行顺序为:


     blk0
     blk1
     blk2
     blk3
     blk4
     blk5


当queue是Concurrent Dispatch Queue时,假设准备3个Concurrent Dispatch Queue开启线程执行顺序为:

Concurrent Dispatch Queue 执行例子.png

由两者比较可以看出,在Concurrent Dispatch Queue执行处理时,执行顺序会根据处理内容和系统状态发生改变,不同于执行顺序固定的Serial Dispatch Queue

得到Dispatch_Queue的方法有两种:一种是通过dispatch_queue_create创建,一种是系统提供的Main Dispatch QueueGlobal Dispatch Queue

dispatch_queue_create

可以通过dispatch_queue_create函数创建串行/并发队列。
避免多个线程更新相同资源导致数据竞争时使用串行队列。想并行执行不发生数据竞争等问题的处理时使用并发队列

创建串行队列:

    dispatch_queue_t queue = dispatch_queue_create("me.rose99.blog", NULL);
    
    dispatch_async(queue, ^{
          NSLog(@"%@", [NSThread currentThread]);
    });

创建并行队列也是如此,只是把第二个参数改为DISPATCH_QUEUE_CONCURRENT的宏即可。

Main Dispatch Queue/Global Dispatch Queue

Main Dispatch Queue是在主线程中执行的队列,因为主线程(用户界面更新必须要放在主线程中执行)只有一个,所以是串行队列。

Global Dispatch Queue是所有应用程序都能使用的并发队列。Global Dispatch Queue有4个执行优先级:

系统提供的Dispatch Queue.png

获取方法如下:

    //主队列的获取方法
    dispatch_queue_t mainDispatchQueue = dispatch_get_main_queue();
    
    //全局并发队列的获取方法
    dispatch_queue_t gobalDispatchQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    
    dispatch_async(gobalDispatchQueue, ^{
        
          //要执行的操作

        dispatch_async(mainDispatchQueue, ^{
              //只能在主线程中执行的操作
        });
    });

变更生成的Dispatch Queue的执行优先级要使用dispatch_set_target_queue函数,如果多个串行队列中用dispatch_set_target_queue函数指定目标为串行队列,可防止处理并行执行。因为原本应并行执行的多个串行队列,在目标队列上只能同时执行一个处理。

    dispatch_queue_t serialDispatchQueue = dispatch_queue_create("me.rose99.blog", NULL);
    dispatch_queue_t gobalDispatchQueueBackground = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0);

    //变更执行优先级,第一个参数是要变更的优先级的queue,第二个参数是目标queue(与执行优先级相同的queue)
    dispatch_set_target_queue(serialDispatchQueue, gobalDispatchQueueBackground);
    

dispatch_after

想在3秒后执行处理可使用dispatch_after函数来实现。

     /*
       dispatch_time_t 是dispatch_time函数/dispatch_walltime的返回值
       第一个参数:指定的时间开始  DISPATCH_TIME_NOW 是指现定从现在的时间开始
       第二个参数:指定的结束时间  3ull *NSEC_PER_SEC 是开始3秒后的dispatch_time_t类型的值, 3NSEC_PER_MSEC 是开始3毫秒后的值 ull是数值字面量(显示表名类型时使用的字符串unsigned long long)
      */
    
      //获取从现在的时间开始到3秒后时间的值
      dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, 3ull *NSEC_PER_SEC);
    
      /*
       dispatch_after不是指定时间后执行,而是在指定的时间追加处理到Dispatch Queue。
       因为Main Dispatch Queue 在主线程的Runloop中执行,若在每隔1/60秒执行的Runloop中,block最快在3秒后执行,最慢在3+1/60秒后执行。不过想大概延迟处理时,该函数还是很有效的
      */
  
       dispatch_after(time, dispatch_get_main_queue(), ^{
             NSLog(@"waited time at least three seconds.");
       });

dispatch_time函数通常用于计算相对时间,dispatch_walltime函数用于计算绝对时间。
比如想在dispatch_after函数中指定2016年11月11号11分11秒这一绝对时间的情况,可以使用dispatch_walltime函数。

NSDate类对象可转换成dispatch_time_t类型的值传递给dispatch_after函数。如下:

    dispatch_time_t getDispatchTimeByDate(NSDate *date)
    
    {
        NSTimeInterval interval;
        double second,subsecond;
        struct timespec time;
        dispatch_time_t milestone;

        interval = [date  timeIntervalSince1970];
        subsecond = modf(interval, &second);
        time.tv_sec = second;
        time.tv_nsec = sub second * NSEC_PER_SEC;
        milestone = dispatch_wait(&time, 0);
        
        return milestone;
    }

Dispatch Group

若想在多个任务异步处理结束后在处理别的任务的情况可以使用Dispatch Group。(比如:我点击个按钮,开启两个异步下载图片的任务,等这两个任务结束后想更新UI)

    //创建并发队列
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

    //创建队列组
    dispatch_group_t group = dispatch_group_create();
    
    dispatch_group_async(group, queue, ^{
        //异步下载第一张图
    });

    dispatch_group_async(group, queue, ^{
        //异步下载第二张图
    });
    
    dispatch_group_notify(group, dispatch_get_main_queue(), ^{
        //主线程更新UI
    });


    //指定等待间隔1秒时做任务处理
    dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, 1ull * NSEC_PER_SEC);
    
    long result = dispatch_wait(group, time);
    
    if (result == 0) {
        //1秒全部处理结束
    }
    
    else
    {
        //还有图片没有下载完成。
    }

dispatch_group_notify函数追加的Block是在Dispatch Group的全部处理之后。

dispatch_group_wait函数用来处理仅等待全部处理任务结束(在指定时间或者指定的Dispatch Group处理任务结束之前,只调用不返回,若指定时间为DISPATCH_TIME_NOW效果和dispatch_group_notify函数相同)。

dispatch_barrier_async(栅栏函数)

dispatch_barrier_async函数的作用:

  • 避免数据竞争
  • 可实现高效率的数据库访问和文件访问

dispatch_barrier_async 需要与__ dispatch_queue_create__创建的Concurrent Dispatch Queue一起使用。


    dispatch_queue_t queue = dispatch_queue_create("me.rose99.blog", DISPATCH_QUEUE_CONCURRENT);
    
    dispatch_async(queue, ^{
          NSLog(@"🍎");
    });
    
    dispatch_async(queue, ^{
          NSLog(@"🍎🍎");
    });
    
    dispatch_barrier_async(queue, ^{
          NSLog(@"🍊");
    });
    
    dispatch_async(queue, ^{
          NSLog(@"🍎🍎🍎");
    });
    dispatch_async(queue, ^{
          NSLog(@"🍎🍎🍎🍎");
    });

   输出结果:
   2016-12-14 17:59:58.898 Dispatch_Queue[4555:232866] 🍎🍎
   2016-12-14 17:59:58.898 Dispatch_Queue[4555:232867] 🍎
   2016-12-14 17:59:58.898 Dispatch_Queue[4555:232867] 🍊
   2016-12-14 17:59:58.898 Dispatch_Queue[4555:232867] 🍎🍎🍎
   2016-12-14 17:59:58.898 Dispatch_Queue[4555:232866] 🍎🍎🍎🍎

__ dispatch_barrier_async__ 函数是等待前面的所有异步函数处理完毕之后才会执行,__ dispatch_barrier_async__之后的函数等待dispatch_barrier_async函数之后才会执行。因为是异步,所以1个苹果和2个苹果的执行完毕顺序有可能不同(同理3个和4个也一样)。

如果是系统提供的Main Dispatch Queue 或者Global Dispatch Queue , Main Dispatch Queue的执行顺序就是依次执行,Global Dispatch Queue 的执行顺序就和dispatch_barrier_async函数和dispatch_async函数就没什么区别了。


   // 换成Main Dispatch Queue   输出结果:
   2016-12-14 18:26:00.674 Dispatch_Queue[5259:258557] 🍎
   2016-12-14 18:26:00.674 Dispatch_Queue[5259:258557] 🍎🍎
   2016-12-14 18:26:00.674 Dispatch_Queue[5259:258557] 🍊
   2016-12-14 18:26:00.674 Dispatch_Queue[5259:258557] 🍎🍎🍎
   2016-12-14 18:26:00.675 Dispatch_Queue[5259:258557] 🍎🍎🍎🍎

   //换成Global Dispatch Queue 输出结果:
   2016-12-14 18:28:07.519 Dispatch_Queue[5328:261570] 🍎
   2016-12-14 18:28:07.519 Dispatch_Queue[5328:261553] 🍊
   2016-12-14 18:28:07.519 Dispatch_Queue[5328:261550] 🍎🍎
   2016-12-14 18:28:07.519 Dispatch_Queue[5328:261551] 🍎🍎🍎
   2016-12-14 18:28:07.519 Dispatch_Queue[5328:261583] 🍎🍎🍎🍎
  

死锁

相互等待执行处理便会造成死锁。例如:

  //在主线程中调用同步主队列造成死锁
  dispatch_queue_t queue = dispatch_get_main_queue();
  
  dispatch_sync(queue, ^{
        NSLog(@"🍌🍌🍌");
  });

  //主线程要执行的Block等待主线程执行的Block执行完毕造成死锁
  dispatch_queue_t queue = dispatch_get_main_queue();
  
  dispatch_async(queue, ^{
      
      dispatch_sync(queue, ^{
          NSLog(@"🍌🍌🍌");
      });

  });

dispatch_apply

__ dispatch_apply__函数与同步函数相同,都会等待全部任务执行结束。


    //获取全局并发队列
   dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
   
   /*
       第一个参数为重复次数
       第二个参数为追加对象的queue
       第三个是要执行的block,block带有参数索引值为了区分重复追加的多个block。
   */ 
   dispatch_apply(7, queue, ^(size_t index) {
         NSLog(@"%zu",index);
   });
   
   NSLog(@"🍌🍌🍌");

  输出结果:
  2016-12-15 14:09:22.730 GCD[14332:136228] 0
  2016-12-15 14:09:22.730 GCD[14332:136444] 1
  2016-12-15 14:09:22.730 GCD[14332:136464] 3
  2016-12-15 14:09:22.730 GCD[14332:136446] 2
  2016-12-15 14:09:22.730 GCD[14332:136228] 4
  2016-12-15 14:09:22.730 GCD[14332:136444] 5
  2016-12-15 14:09:22.731 GCD[14332:136464] 6
  2016-12-15 14:09:22.731 GCD[14332:136228] 🍌🍌🍌

由于是并发队列,各个处理的执行时间不定,但🍌🍌🍌一定是最后输出,因为dispatch_apply函数会等待全部任务执行结束。如果是串行队列,处理会按照顺序依次执行。

dispatch_apply函数可以提高性能避免线程爆炸以及死锁的情况。

      //使用for循环遍历可能导致线程爆炸以及死锁
      for (int index = 0; i index < 9999;  index +){
          dispatch_async(queue, ^{
                //执行的操作
          });
      }
      dispatch_barrier_sync(queue, ^{
            //执行的操作
      });

      //使用dispatch_apply函数可管理并发
      dispatch_apply(9999, queue, ^(size_t index){
                //执行的操作
      });

dispatch_apply函数可以快速批量处理数组里的元素。如下:

  //假设有个存有大量对象的数组
  NSMutableArray *array;
  
  dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
  
  dispatch_async(queue, ^{
      
        dispatch_apply(array.count, queue, ^(size_t index) {
              //并列处理数组中的对象
              [array objectAtIndex:index];
        });
      
        //dispatch_apply执行结束后 异步主队列刷新UI
        dispatch_async(dispatch_get_main_queue(), ^{
              //主线程刷新UI
        });
  });

dispatch_suspend/dispatch_resume

想要暂停或者恢复队列的时候,会用到这两个函数。对于已经执行的处理没有影响,暂停队列后,没有执行的处理会停止执行。恢复队列的函数就是使暂停后没有执行的处理能继续执行。

    dispatch_suspend(queue); //暂停某个队列的处理
    dispatch_resume(queue); //恢复某个队列的处理

Dispatch Semaphore

Dispatch Semaphore是持有计数的信号,可以用来处理并行执行更新数据时,产生数据不一致以及一部分处理需要进行排他控制的问题。计数为0是等待,计数>=1时,把计数减去1不等待。

    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

     //生成Dispatch Semaphore 保证可访问数组的线程同时只能有1个
    dispatch_semaphore_t semaphore = dispatch_semaphore_create(1);
   
    NSMutableArray *array = [[NSMutableArray alloc] init];
   
    for (int i = 0; i < 10000; ++i) {
       
          dispatch_async(queue, ^{

           //等待Dispatch Semaphore的计数值达到>=1
           dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
           
           //当Dispatch Semaphore计数值>=1时,再减去1。dispatch_semaphore_wait函数返回。此时计数为0,访问数组的线程只有1个,所以可以安全更新。
           [array addObject:[NSNumber numberWithInt:i]];
           
           //dispatch_semaphore_signal函数可以将Dispatch Semaphore的计数+1。(如果有通过dispatch_semaphore_wait函数等待Dispatch Semaphore的计数值增加的线程,就由最先等待的线程执行。)
           dispatch_semaphore_signal(semaphore);
      
           });
      
    }

dispatch_once

不管单线程,还是多线程,dispatch_once函数是保证在应用程序执行中只会执行一次处理。dispatch_once可使用在单例、缓存等处理。

      /*
        第一个参数保证只会执行一次,
        第二个参数是要执行的Block
      */
      static dispatch_once_t onceToken;
      dispatch_once(&onceToken, ^{
            //只会执行一次的操作
      });

dispatch_once的主要处理情况如下:

  1. 线程A执行Block时,任何其它线程都需要等待。
  • 线程A执行完Block应该立即标记任务完成状态,然后遍历信号量(Dispatch Semaphore)链来唤醒所有等待线程。
  • 线程A遍历信号量链来signal时,任何其他新进入函数的线程都应该直接返回而无需等待。
  • 线程A遍历信号量链来signal时,若有其它等待线程B仍在更新或试图更新信号量链,应该保证此线程B能正确完成其任务:a.直接返回 b.等待在信号量上并很快又被唤醒。
  • 线程B构造信号量时,应该考虑线程A随时可能改变状态(“等待”、“完成”、“遍历信号量链”)。
  • 线程B构造信号量时,应该考虑到另一个线程C也可能正在更新或试图更新信号量链,应该保证B、C都能正常完成其任务:a.增加链节并等待在信号量上 b.发现线程A已经标记“完成”然后直接销毁信号量并退出函数。

Dispatch I/O

读取较大文件时,可以使用多个线程更快的并列读取。Dispatch I/O可以提高文件的读取速度。用到的函数:

  • dispatch_io_create 生成Dispatch I/O
  • dispatch_io_set_low_water 指定一次读取的大小(分割大小)
  • dispatch_io_read 读取文件。

Dispatch Source

Dispatch Source的种类:

Dispatch Source的种类.png

dispatch source函数是一个监视某些类型事件(用户事件、内件事件、计时器等)的对象。当这些事件发生时,它自动将一个block放入一个dispatch queue的执行例程中。

Dispatch source函数启动时默认状态是挂起的,我们创建完毕之后得主动恢复(调用dispatch_resume恢复函数 ),否则事件不会被传递,也不会被执行。

  • 用户事件
    有两种:
       DISPATCH_SOURCE_TYPE_DATA_ADD  //变量增加 (事件联结时会把数字相加)
       DISPATCH_SOURCE_TYPE_DATA_OR  //变量OR (事件联结时会把数字进行逻辑运算)
    
*  内件事件
    dispatch_queue_t globalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

    //创建read事件的Dispatch Source
    dispatch_source_t readSource = dispatch_source_create(DISPATCH_SOURCE_TYPE_READ,STDIN_FILENO,0,globalQueue);

    //指定read事件执行的Block
    dispatch_source_set_event_handler(readSource, ^{
         char buff[1024];
    //从映像中读取
    int length = read(STDIN_FILENO, buff, sizeof(buff));
    if(length < 0)
    {
        //错误处理...处理结束取消Dispatch Source
        dispatch_source_cancel(readSource);
    }
   });
     //启动Dispatch Source
    dispatch_resume(readSource);
*  定时器
  //创建DISPATCH_SOURCE_TYPE_TIMER 作为Dispatch Source
  dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, dispatch_get_main_queue());

  //指定定时器5秒后执行,允许延迟1s,不指定为重复
  dispatch_source_set_timer(timer,dispatch_time(DISPATCH_TIME_NOW, 25ull*NSEC_PER_SEC), DISPATCH_TIME_FOREVER, 1ull*NSEC_PER_SEC);

  //指定时间内执行Block
  dispatch_source_set_event_handler(timer, ^{
        NSLog(@" pipe apple 🍎");
        dispatch_source_cancel(timer);
  });

  //取消处理
  dispatch_source_set_cancel_handler(timer, ^{
        NSLog(@"canceled pipe apple 🍎");
  });

  //必须主动启动source才会执行
  dispatch_resume(timer);

###关于GCD的一些问题
1.dispatch_async 函数如何实现,分发到主队列和全局队列有什么区别,一定会新建线程执行任务么?

__dispatch_async 函数的实现:会把任务添加到队列的一个链表中,添加完后会唤醒队列,根据 vtable (链表)中的函数指针,调用 wakeup 方法。在 wakeup 方法中,从线程池里取出工作线程(如果工作线程是主线程就不用新建,没有就新建线程执行任务),然后在工作线程中取出链表头部指向的 block 并执行 。
分发到主队列的任务由Runloop处理,分发到全局队列的任务由线程池处理。__  

2.dispatch_sync 函数如何实现,为什么说 GCD 死锁是队列导致的而不是线程?

__dispatch_sync 函数的实现:不涉及线程池(一般都是在当前线程执行),利用与线程绑定的信号量实现串行。GCD导致死锁:__

//在当前串行队列中执行 dispatch_sync 时,dq_running (表示在运行的任务数量) 为 1
if (slowpath(!dispatch_atomic_cmpxchg2o(dq, dq_running, 0, 1))) {

// _dispatch_barrier_sync_f_slow 函数中使用了线程对应的信号量并且调用 wait 方法,从而 导致线程死锁。
return _dispatch_barrier_sync_f_slow(dq, ctxt, fun);

}

如果换成其他队列提交Block,则进入 _dispatch_barrier_sync_f_invoke函数,它只是保证了 block 执行的原子性,但没有使用线程对应的信号量。


3.信号量是如何实现的,有哪些使用场景?


__主要是用dispatch_semaphore_signal(信号量计数+1,唤醒线程返回非0,否则返回0)和dispatch_semaphore_wait(信号量计数-1)这两个函数,底层调用了内核提供的方法。初始 value 必须大于等于 0,如果为 0 并随后调用 dispatch_semaphore_wait 方法,线程将被阻塞直到别的线程调用了 dispatch_semaphore_signal 方法。__PS:dispatch_semaphore_wait函数返回0表示成功,返回其他则表示超时。

__使用场景:dispatch_group/dispatch_once等.__

4.dispatch_group 的等待与通知、dispatch_once 、dispatch_barrier_async如何实现?

__dispatch_group: 本质就是一个 value 非常大的信号量,等待 group 完成实际上就是等待 value 恢复初始值。而 notify 的作用是将所有注册的回调组装成一个链表,在 dispatch_async 完成时判断 value 是不是恢复初始值,如果是则调用 dispatch_async 异步执行所有注册的回调。__

__dispatch_once 通过一个静态变量来标记 block 是否已被执行,同时使用信号量确保只有一个线程能执行,执行完 block 后会唤醒其他所有等待的线程。__

__dispatch_barrier_async 改变了 block 的 vtable(链表) 标记位,当它将要被取出执行时,会等待前面的 block 都执行完,然后在下一次循环中被执行。__

5.dispatch_source 用来做定时器如何实现,有什么优点和用途?

__实现:source 会被提交到用户指定的队列,然后提交到 manager 队列中,按照触发时间排好序。随后找到最近触发的定时器,调用内核的 select 方法等待。等待结束后,依次唤醒 manager 队列和用户指定队列,最终触发一开始设置的回调 block。
优点:不依赖Runloop,因此任何线程都可以使用。使用了 block块,不会忘记避免循环引用。用处:定时器可以自由控制精度,随时修改间隔时间等。__

6.dispatch_suspend 和 dispatch_resume 如何实现,队列的的暂停和计时器的暂停有区别么?

__dispatch_suspend (将do_suspend_cnt的值+2)和 dispatch_resume 将do_suspend_cnt的值-2)由do_suspend_cnt属性决定,默认值有两个:__


     #define DISPATCH_OBJECT_SUSPEND_LOCK        1u (启动)
     #define DISPATCH_OBJECT_SUSPEND_INTERVAL    2u(暂停)

__dispatch_source的定时器默认值是暂停的(DISPATCH_OBJECT_SUSPEND_INTERVAL),需要手动开启,而队列的默认值(DISPATCH_OBJECT_SUSPEND_LOCK)默认启动的。__


参考:
https://bestswifter.com/deep-gcd/
Objective-C高级编程 iOS与OS X多线程和内存管理
http://www.jianshu.com/p/c2b14bb999de

相关文章

  • 多线程之GCD

    GCD介绍 1、GCD简介 2、GCD任务和队列 3、GCD 的基本使用 4、GCD 线程间的通信 5、GCD 的...

  • 扩展GCD(求逆元,解同余方程等等)

    首先要知道gcd函数的基本性质:gcd(a,b)=gcd(b,a)=gcd(|a|,|b|)=gcd(b,a%b)...

  • iOS - GCD

    目录 GCD简介 GCD核心概念 GCD队列的使用 GCD的常见面试题 GCD简介 Grand Central D...

  • iOS-多线程:GCD

    GCD 简介 GCD 任务和队列 GCD 的使用步骤 GCD 的基本使用(6种不同组合区别) GCD 线程间的通信...

  • 浅析GCD

    GCD目录: 1. GCD简介 为什么要用GCD呢? GCD可用于多核的并行运算GCD会自动利用更多的CPU内核(...

  • 7.3 多线程-GCD

    多线程-GCD 多线程-GCD-串行并行 多线程-GCD.png GCD-线程的通讯、延时操作、定时器 GCD-线...

  • iOS 多线程--GCD

    一、GCD基本介绍 1.GCD简介 GCD是Grand Central Dispatch的缩写,GCD是苹果推出的...

  • 自用算法模板(JAVA版)

    一、数论 1)GCD GCD(求最大公约数) QGCD(快速GCD) extGCD(拓展GCD,解决ax + by...

  • GCD介绍

    一、GCD简单介绍 什么是GCD GCD优势 任务和队列 GCD有2个核心概念 GCD的使用就2个步骤 将任务添加...

  • 7.多线程基础(七)GCD加强

    1.GCD串行队列和并发队列 2.GCD延时执行 3.GCD线程组:(的作用) 4.GCD定时器: GCD的实现 ...

网友评论

      本文标题:GCD

      本文链接:https://www.haomeiwen.com/subject/toxbmttx.html