美文网首页
二 GCD的基础使用以及相关API

二 GCD的基础使用以及相关API

作者: 贺赟生 | 来源:发表于2020-03-25 18:23 被阅读0次

2.1Dispatch Queue

正如字面意思,意为执行处理的等待队列。我们通过dispatch_async函数等API在block中想要执行的处理追加到Dispatch Queue中,而Dispatch Queue是按照FIFO执行处理。

执行处理时有两中Dispatch Queue: Serial Dispatch Queue 等待现在执行中处理结束 (串行队列),Concurrent Dispatch Queue 不等待现在执行中处理结束(并行队列)

例如(伪代码):

    queue为Serial Dispatch Queue
    dispatch_async(queue, block1);
    dispatch_async(queue, block2);
    dispatch_async(queue, block3);
    dispatch_async(queue, block4);
    dispatch_async(queue, block5);
    dispatch_async(queue, block6);

执行顺序为 block1 block2 ... 到 block6

若queue为Concurrent Dispatch Queue 则不会等正在执行中的处理结果 ,不管block1知否执行完毕 都会继续走block2 如此类推

2.2 dispatch_queue_create

生成Dispatch Queue可以通过 GCD的API来生成,通过dispatch_queue_create 函数就可生成 Dispatch Queue
例如 如下生成一个串行队列

   dispatch_queue_t myQueue = dispatch_queue_create("com.test.myQueue", NULL);

其中通过create创建多个 serial Dispatch Queue时 各个Queue将并行执行,所以按照之前说的串行队列只使用一个线程,如果创建多个那就会有多个线程势必消耗巨大内存。所以在之前多线程同时更新相同资源的问题是就可以使用serial disptatch queue 来解决。

但是需要注意的是串行队列生成的个数仅限所必须的数量。例如更新数据库时一个表对应一个queue,更新文件时一个文件对一个queue,决不能生成大量的serial Dispatch Queue。

dispatch_queue_create 函数

  dispatch_queue_create(const char *_Nullable label,dispatch_queue_attr_t _Nullable attr);

第一个参数为Serial Dispatch Queue的名称,而名称命名规则则推荐使用逆序全称域名法(FQDN fully qualified domain name)。主要是为了在以后的调试中清楚易懂,而且在崩溃日志中也容易查到。
第二个参数即为要生产Serial 还是 Concurrent Dispatch Queue 。如果传入NULL则生成 Serial Dispatch Queue ,如传入 DISPATCH_QUEUE_CONCURRENT 则生成 Concurrent Dispatch Queue。
例如 如下生成一个串行队列

   dispatch_queue_t myConcurrentQueue = dispatch_queue_create("com.test.myConcurrentQueue", DISPATCH_QUEUE_CONCURRENT);

返回值为表示Dispatch Queue 的 dispatch_queue_t类型

需要注意的是虽然苹果提供了ARC内存管理,但是生成的Dispatch Queue必须有程序员释放,使用dispatch_queue_create创建Dispatch Queue结束时候需要用dispatch_release函数来释放。例如

  dispatch_queue_t myConcurrentQueue = dispatch_queue_create("com.test.myConcurrentQueue", DISPATCH_QUEUE_CONCURRENT);
    dispatch_async(myConcurrentQueue, ^{
        NSLog(@"testGCD");
    });
    dispatch_release(myConcurrentQueue);

需要注意的是
如果你部署的最低目标低于 iOS 6.0 or Mac OS X 10.8
你应该自己管理GCD对象,使用(dispatch_retain,dispatch_release),ARC并不会去管理它们
如果你部署的最低目标是 iOS 6.0 or Mac OS X 10.8 或者更高的
ARC已经能够管理GCD对象了,这时候,GCD对象就如同普通的OC对象一样,不应该使用dispatch_retain ordispatch_release

2.3 Main Dispatch Queue /Global Dispatch Queue

获取系统提供的Dispatch Queue : Main Dispatch Queue ,Global Dispatch Queue。Main中的处理既会在主线程的runloop中执行所以 操作界面UI的处理需要追加到Main Dispatch Queue中处理。所以Main Dispatch Queue 是Serial Dispatch Queue。Global Dispatch Queue 是所有应用程序都能够使用的Concurrent Dispatch Queue。Global Dispatch Queue有4个优先级 分别为 高、默认、低以及后台优先级。

获取方式:

   //main
   dispatch_queue_t mainQueue = dispatch_get_main_queue();
   //global  高优先级
   dispatch_queue_t globalQueueHigh = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0);
      //global 默认先级
   dispatch_queue_t globalQueueDefult = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
      //global  低先级
   dispatch_queue_t globalQueueLow = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0);
      //global  后台先级
   dispatch_queue_t globalQueueBackGround = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0);

使用实例

dispatch_async(globalQueueDefult, ^{
        //并行处理
        //...
        //回到主线程处理
        dispatch_async(mainQueue, ^{
            //回到主线程需要处理的操作
        });
    });

2.4 dispatch_set_target_queue

该函数为变更生成的Dispatch Queue的执行优先级,因为通过create生成的Dispatch Queue 都为默认优先级。例如:

dispatch_queue_t mySerialQueue = dispatch_queue_create("com.test.mySerialQueue", NULL);
dispatch_queue_t globalQueueBackGround = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0);
//变更生成Dispatch Queue 执行优先级
    dispatch_set_target_queue(mySerialQueue, globalQueueBackGround);

其中第一个参数为需要变更的Dispatch Queue,第二参数即为想要变成此Dispatch Queue的优先级。需要注意的是第一个参数不可为系统优先级的Dispatch Queue,即Main 、Global Dispatch Queue。

2.5 dispatch_after

想在指定时间后执行处理,使用dispatch queue 函数来实现。API如下

dispatch_after(dispatch_time_t when, dispatch_queue_t queue, dispatch_block_t block);

举个栗子

dispatch_time_t time = dispatch_time(DISPATCH_WALLTIME_NOW, 3ull * NSEC_PER_SEC);
    dispatch_after(time, dispatch_get_main_queue(), ^{
        NSLog(@"3秒后执行");
    });

此函数并不是在指定的时间后执行处理,而是在指定的时间追加处理到第二个参数Dispatch Queue 中。上面的例子与3秒后使用dispatch_async函数追加Block到Main Dispatch Queue效果一样。

api中第一个参数为为dispatch_time_t类型,该类型可以通过dispatch_time函数或者dispatch_walltime 函数生成,其中dispatch_time函数中第一个参数指定的时间开始,执行到第二个参数指定的毫微秒单位的时间后。其中 数字后面“ull”为C语言的数值字面量,显示表明类型时使用的字符串(表示 “unsigned long long”)

2.6 Dispatch Group

使用场景为追加到Dispatch Queue中多个处理全部结束后想执行结束处理,常用在Concurrent Dispatch Queue 或者同时使用多个Dispatch Queue时。例如:追加3个block到Global Dispatch Queue,这些block执行完成在执行Main Dispatch Queue中结束处理用的block。

dispatch_queue_t queue1 = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    dispatch_group_t group1 = dispatch_group_create();
    dispatch_group_async(group1, queue1, ^{
        NSLog(@"aaaaa");
    });
    dispatch_group_async(group1, queue1, ^{
        NSLog(@"bbbbb");
    });
    dispatch_group_async(group1, queue1, ^{
        NSLog(@"ccccc");
    });
    dispatch_group_notify(group1, dispatch_get_main_queue(), ^{
        NSLog(@"finished");
    });

结果

2020-03-20 15:49:57.169567+0800 GCD learn[70587:22349247] bbbbb
2020-03-20 15:49:57.169567+0800 GCD learn[70587:22349250] aaaaa
2020-03-20 15:49:57.169575+0800 GCD learn[70587:22349245] ccccc
2020-03-20 15:49:57.183424+0800 GCD learn[70587:22349122] finished

对于执行顺序问题,因为Global Dispatch Queue 即 Concurrent Dispatch Queue,多个线程并行处理故执行顺序不确定,但是finished一定是最后执行。所以无论什么样的Dispatch Queue,使用group都可以监视这些处理的结束。一旦检测到所有的处理结束,就可将结束的处理追加到Dispatch Queue中。
其中dispatch_group_async与dispatch_async函数相同都是追加block到指定的queue。与之不同的是前者需要指定生成的Dispatch Queue为第一个参数。其中指定的block属于指定的Dispatch Queue。
在追加到Dispatch Queue的处理全部结束时,使用dispatch_group_notify函数将执行的block追加到Dispatch Queue中,其中第一个参数为要监控的的group。在追加到该group的全部处理结束时候,将第三个参数block追加到第二个参数queue中。
Dispatch Group也可以使用

dispatch_group_wait(dispatch_group_t group, dispatch_time_t timeout)

函数来等待全部处理执行结束。
第二个参数为等待的时间(也可理解为超时时间)其中DISPATCH_TIME_FOREVER为永久等待,就是只要Dispatch Queue的处理未执行结束就一直等待,中途不能取消。
和dispatch_after中出现的一样。例如:

dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, 1ull * NSEC_PER_SEC);
    long result = dispatch_group_wait(group1, time);
    if (result == 0) {
        //group1 的全部处理执行结束之后执行改代码
    }else{
        //group1 的某一个处理还在执行中
    }

如果dispatch_group_wait函数返回值不为0 意味着虽然经过了指定的时间,group的一个处理还咋执行。如果为0证明一件全部执行完毕。所以执行dispatch_group_wait 现在的线程会停止,等指定的group执行完成才会继续,所以一般情况下还是推荐使用dispatch_group_notify可以简化代码。

2.7dispatch_barrier_async

该函数和生成的Concurrent Dispatch Queue一起使用 ,通常是为了在dispatch_async中追加处理 。例如:

dispatch_queue_t queue = dispatch_queue_create("com.example.gcd.barrier", DISPATCH_QUEUE_CONCURRENT);
    dispatch_async(queue, ^{ NSLog(@"读取操作1");});
    dispatch_async(queue, ^{ NSLog(@"读取操作2");});
    dispatch_async(queue, ^{ NSLog(@"读取操作3");});
    /*
     如果在操作3和操作4之间添加一个写入操作 如果直接使用 dispatch_async(queue, ^{ NSLog(@"写入操作");}); 由于是并行队列
这样很可能导致读取与写入的数据不符合,而且多个写入操作有可能造成数据竞争等问题 所以使用dispatch_barrier_async
     */
    dispatch_barrier_async(queue, ^{ NSLog(@"写入操作");});
    dispatch_async(queue, ^{ NSLog(@"读取操作4");});
    dispatch_async(queue, ^{ NSLog(@"读取操作5");});

执行结果

2020-03-24 15:18:32.326868+0800 GCD learn[9315:727366] 读取操作1
2020-03-24 15:18:32.326871+0800 GCD learn[9315:727369] 读取操作3
2020-03-24 15:18:32.326880+0800 GCD learn[9315:727367] 读取操作2
2020-03-24 15:18:32.327033+0800 GCD learn[9315:727367] 写入操作
2020-03-24 15:18:32.327160+0800 GCD learn[9315:727367] 读取操作4
2020-03-24 15:18:32.327183+0800 GCD learn[9315:727366] 读取操作5

所以dispatch_barrier_async函数会等待其之前的并行队列执行完成之后,然后执行dispatch_barrier_async的处理,等执行完毕之后然后在回复执行Concurrent Dispatch Queue的处理开始并行执行。所以Concurrent Dispatch Queue和dispatch_barrier_async可以实现高效率的数据库访问已经文件的访问。

2.8 dispatch_sync

dispatch_sync同步队列,也就说将制定的block追加到指定的Dispatch Queue中。在block结束之前dispatch_sync会一直等待。其中这样的场景需要使用dispatch_sync函数,如:执行Main Dispatch Queue时 使用另外的线程Global Dispatch Queue进行处理,处理结束后立即使用所得到的结果。

dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    dispatch_sync(queue, ^{
        NSLog(@"处理");
    });

调用dispatch_sync函数后,在指定处理执行结束之前 函数将不会返回,也可理解为简易的dispatch_group_wait函数。但是也是容易出现死锁情况,如下:

    //死锁一
    dispatch_queue_t queueMian  = dispatch_get_main_queue();
    dispatch_sync(queueMian, ^{
        NSLog(@"死锁?");
    });
    //死锁二
    dispatch_async(queueMian, ^{
        dispatch_sync(queueMian, ^{
            NSLog(@"死锁");
        });
    });
    //死锁三
    dispatch_queue_t serialQueue = dispatch_queue_create("com.example.gcd", NULL);
    dispatch_async(serialQueue, ^{
        dispatch_sync(serialQueue, ^{
            NSLog(@"死锁");
        });
    });

例子 都是在主线程执行指定的block并等待结束,而这些代码正在主线程中执行,所以无法追加到main的block,故而造成死锁。

2.9 dispatch_apply

该函数是dispatch_sync函数和Dispatch Queue的关联API。按照指定的次数将指定的block追加到指定的Dispatch Queue中,并且等待全部处理执行完毕。例如:

dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    dispatch_apply(10, queue, ^(size_t index) {
        NSLog(@"%zu",index);
    });
NSLog(@"done");

结果:0、4、5 ... 7 、done。
因为在global中执行处理,所以个处理的执行时间并不一定,但是最后的done 一定是在最后执行,因为dispatch_apply函数会等待全部处理结束才继续往下走。
该函数第一个参数为重复次数,第二个参数追加的对象Dispatch Queue,第三个参数为追加的处理。因为dispatch_apply会等待处理全部结束在执行之后的代码,所以推荐在dispatch_async函数中异步执行dispatch_apply函数。例如:

    NSArray *array = [NSArray array];
    //在Global Dispatch Queue中异步执行
    dispatch_async(queue, ^{
        //等dispatch_apply函数中全部处理执行完毕
        dispatch_apply(array.count, queue, ^(size_t index) {
            //并列处理数组array的全部对象
            NSLog(@"%zu  %@",index,array[index]);
        });
        //dispatch_apply函数中处理全部结束,然后会到main中异步处理
        dispatch_async(dispatch_get_main_queue(), ^{
            //处理更新界面等处理
            NSLog(@"done");
        });
    });

2.10 dispatch_suspend / dispatch_resume

dispatch_suspend挂起指定的Dispatch Queue
dispatch_resume恢复指定的Dispatch Queue

2.11 Dispatch Semaphore

Dispatch Semaphore是持有计数的信号,该计数是多线程编程中的计数类型信号。Dispatch Queue中使用计数来实现等待或者不等待,计数为0等待 ,计数>=1时,减1而不等待。初始化如下:

dispatch_semaphore_t semaphore = dispatch_semaphore_create(long value);

其中参数标识计数的初始值。

dispatch_semaphore_wait(dispatch_semaphore_t dsema, dispatch_time_t timeout);

该函数则为等待Dispatch Queue ,Semaphore的计数值达到大于或者等于1,对该计数进行减法并且从dispatch_semamphore函数返回。第二个参数如同dispatch_group_wait函数中参数类型一样为等待时间。其返回值也与dispatch_group_wait返回值相同,可以通过返回值进行分支处理。例如:

    //通过dispatch_semaphore_wait返回值进行分支处理
    dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, 1ull*NSEC_PER_SEC);
    long result = dispatch_semaphore_wait(semaphore, time);
    if (result == 0) {
        //由于Dispatch Semaphore的计数值达到大于等于1或者在待机中的指定时间内Dispatch Semaphore的计数减1.
        //....
        //可以执行需要排他控制的处理
    }else{
        //此时计数为0 因此在达到指定时间为止待机
    }

dispatch_semaphore_wait函数返回0时,可以安全执行需要进行排他控制的处理。处理结束后通过dispatch_semaphore_signal函数将计数加1。例如:

dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    dispatch_semaphore_t semaphoreA = dispatch_semaphore_create(1);
    NSMutableArray *array = [NSMutableArray array];
    for (NSInteger i = 0; i< 100000; ++i) {
        dispatch_async(queue, ^{
            //等待dispatch semaphore 一直等待其计数值达到>=1
            dispatch_semaphore_wait(semaphoreA, DISPATCH_TIME_FOREVER);
            //由于dispatch semaphore计数大于等于1 所以将dispatch semaphore的计数减1 dispatch semaphore函数执行返回
            
            //执行到这dispatch semaphore计数已为0.由于可访问数组对象的线程只有1个所以可以安全更新
            [array addObject:@(i)];
            
            //排他控制处理结束后通过dispatch_semaphore_signal函数将计数加一
            dispatch_semaphore_signal(semaphoreA);
        });
    }

所以在没有Serial Dispatch Queue和dispatch_barrier_async函数那么大粒度且一部分处理需要进行排他控制的情况下,推荐使用Dispatch Semaphore

2.12 dispatch_once

dispatch_once函数是保证在应用程序执行中只执行一次指定处理。例如:

//常规初始化
    static int initialized = NO;
    if (initialized == NO) {
        //初始化操作
        initialized = YES;
    }

使用dispatch_once函数之后可简化为:

//使用dispatch_once函数初始化
    static dispatch_once_t pred;
    dispatch_once(&pred, ^{
        //初始化代码
    });

在多线程环境下通过dispatch_once函数,可以保证百分之百安全。这也就是单例模式,生成单例对象使用。

2.13 Dispatch I/O

在读取大文件的时候,将文件分成合适大小的Global Dispatch Queue并列读取,读取速度并不会快很多,而现在就可以使用 Dispatch I/O 和Dispatch Data来实现大文件的读取。
将文件分割成一块块进行读取,使用Dispatch Data可以对数据进行结合和分割。

相关文章

网友评论

      本文标题:二 GCD的基础使用以及相关API

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