19、iOS底层分析 - GCD(二)应用分析

GCD应用分析


1、dispatch_group_t  组 

GCD线程同步

例如:同时进行多个网络请求(例:任务1、任务2),这几个任务互相不想干,但是等任务全部完成之后再执行接下来的任务,这种场景下,最常用的就是GCD 的group
 1、dispatch_group_async
 通过dispatch_group_async() 向组中添加任务1、任务2,然后通过dispatch_group_notify() 来执行任务1、任务2都结束之后接下的的任务。

dispatch_queue_t globalQueue = dispatch_get_global_queue(0, 0);
    dispatch_group_t group = dispatch_group_create();
    dispatch_group_async(group, globalQueue, ^{
        sleep(1);
        NSLog(@"task 1");
    });
    
    dispatch_group_async(group, globalQueue, ^{
        sleep(1);
        NSLog(@"task 2");
    });
//全部执行完毕
    dispatch_group_notify(group, globalQueue, ^{
        NSLog(@"task done");
    });


 2、进组和出组
    进组:dispatch_group_enter(),(输入)告诉 group 要添加任务到队列中了
    出组:dispatch_group_leave(),(离开)告诉 group 一个任务执行完了,将其移出组
    等待:dispatch_group_wait(),  (等待) 阻塞当前线程,当leave 的次数与 enter 次数相等或超过时候才会继续往下执行。
    完成:dispatch_group_notify(),(通知)leave 的次数与 enter 的次数相等时,会执行其中的任务。
 因此dispatch_group_leave() 和 dispatch_group_wait() 要成对使用,
 上例中的需求如果要求先完成任务1 之后再执行任务2,可修改如下:

dispatch_queue_t globalQueue = dispatch_get_global_queue(0, 0);
    dispatch_group_t group = dispatch_group_create();
    
    dispatch_group_enter(group);
//    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)), globalQueue, ^{
////        sleep(1);
//        NSLog(@"task 1");
//        dispatch_group_leave(group);
//    });
    dispatch_async(globalQueue, ^{
        sleep(2);
        NSLog(@"task 1");
        dispatch_group_leave(group);
    });
//永远等待,也就是当任务1不出组(移出),当前线程就永远等待。
    dispatch_group_wait(group, DISPATCH_TIME_FOREVER);

    dispatch_group_enter(group);
    dispatch_async(globalQueue, ^{
        NSLog(@"task 2");
        dispatch_group_leave(group);
    });
//    NSLog(@"task 2");
//    dispatch_group_leave(group);
    
    //全部执行完毕。非线程阻塞
    dispatch_group_notify(group, globalQueue, ^{
        NSLog(@"task done");
    });
    NSLog(@"是否阻塞?");

dispatch_group_notify 函数不会阻塞线程,dispatch_group_wait 会阻塞线程。
dispatch_group_notify 函数可以直接添加任务,dispatch_group_wait 函数只是单纯的阻塞了线程继续向下执行,所以需要自己另行添加“总结性任务”
 dispatch_group_notify 函数中的参数 queue 不论是否与 dispatch_group_async 是相同的queue队列,都会最后执行


2、dispatch_barrier_async 

阻碍 同步 和 阻塞异步

2、dispatch_barrier_async  阻碍
 这一系类API 只对通过dispatch_queue_create() 创建出来的 DISPATCH_QUEUE_CONCURRENT(并发)队列有效,如果是其他队列,比如全局或者不是通过 dispatch_queue_create()创建的 并发队列,那么dispatch_barrier_async / dispatch_barrier_sync 就会变成 dispatch_async / dispatch_sync 一样,失去特殊性,也就是说 dispatch_barrier_async 没有 “栅栏”的作用了。

    dispatch_queue_t queue = dispatch_queue_create("com.liujilou.barrier", DISPATCH_QUEUE_CONCURRENT);
    __block int a = 10;
    dispatch_async(queue, ^{
        NSLog(@"读取1  a=%d",a);
    });
    dispatch_async(queue, ^{
        NSLog(@"读取2  a=%d",a);
    });
    dispatch_async(queue, ^{
        NSLog(@"读取3  a=%d",a);
    });
//
    dispatch_barrier_async(queue, ^{
        sleep(1);
        NSLog(@"赋值 a=%d",a);
        a = 20;
    });
    NSLog(@"主线程执行");
    dispatch_async(queue, ^{
        NSLog(@"读取4  a=%d",a);
    });
    dispatch_async(queue, ^{
        NSLog(@"读取5  a=%d",a);
    });
/*
 2020-03-15 15:16:21.510753+0800 filedome[73509:2023353] 主线程执行
 2020-03-15 15:16:21.510763+0800 filedome[73509:2023414] 读取1  a=10
 2020-03-15 15:16:21.510781+0800 filedome[73509:2023415] 读取2  a=10
 2020-03-15 15:16:21.510783+0800 filedome[73509:2023416] 读取3  a=10
 2020-03-15 15:16:22.515361+0800 filedome[73509:2023416] 赋值 a=10
 2020-03-15 15:16:22.515739+0800 filedome[73509:2023415] 读取5  a=20
 2020-03-15 15:16:22.515736+0800 filedome[73509:2023416] 读取4  a=20
 */

通过打印结果分析可知,dispatch_barrier_async 之前添加到队列的任务会并发执行(读取1-3),并且在这些任务执行完成之后才会执行 dispatch_barrier_async 中的任务。同时 dispatch_barrier_async 会阻塞当前线程,需要直线完其中的任务z才能继续后面的这些,但是不会阻塞主线程,所以主线程能正常执行。执行完后面的任务继续并发执行。
 图 dispatch_barrier_async.png
 dispatch_barrier_sync 会阻塞主线程。


3、dispatch_suspend() 和 dispatch_resume()

挂起、恢复队列

dispatch_suspend(queue) 挂起指定的队列,(在一起不需要执行过度操作的地方,先挂起队列,然后再恢复队列);
 dispatch_resume(queue) 恢复执行队列
 对已经执行的处理没有影响。挂起后,追加到队列中,还没有执行的任务会被挂起直到恢复。恢复后处理继续执行。
 dispatch_suspend并不会立即暂停正在运行的block,而是在当前block执行完成后,暂停后续的block执行。
 要成对使用,否者会报错。

4、dispatch_semaphore_create 信号量

信号量就是信号 >=1的时候,允许通过,否者不允许通过。是多线程的一种锁机制。

//    1、创建信号量为1
    dispatch_semaphore_t semaphoer = dispatch_semaphore_create(1);
//    2、等待 加锁
//    第一个参数 信号量。信号量 >=1 的时候允许通过。并对其进行 减1操作。
//    等待 第二个参数等待时间,这里用一个未来的时间也就是一直等待。
    dispatch_semaphore_wait(semaphoer, DISPATCH_TIME_FOREVER);
//    3、解锁
//    计数加1,相当于解锁。在任务执行完可以再次访问的时候进行加1 解锁
    dispatch_semaphore_signal(sem);
//    这三个步骤必须都有,否者会崩溃。
    dispatch_semaphore_t sem = dispatch_semaphore_create(1);
    //    计数 减 1
    long result = dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);
    if (result == 0) {
        NSLog(@"task  1");
        //        计数 加 1
        dispatch_semaphore_signal(sem);
    }else{
        //        计数不为0的情况进行处理
        NSLog(@"task  2");
    }
dispatch_queue_t queue = dispatch_queue_create(0, 0);
    dispatch_semaphore_t sem = dispatch_semaphore_create(1);
    dispatch_async(queue, ^{
        for (int i=0; i<10000; i++) {
            dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);
            self.num++;
            dispatch_semaphore_signal(sem);
            NSLog(@"%d",self.num);
        }
    });
    
    dispatch_async(queue, ^{
        for (int i=0; i<10000; i++) {
            dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);
            self.num++;
            dispatch_semaphore_signal(sem);
            NSLog(@"%d",self.num);
        }
    });
//   这样就能打印到20000,如果不加锁中间可能会有同时访问拿到同样的值进行操作,这样得到的最终结果就不一定是20000,线程安全。
//    同时信号量也能控制开辟线程的数量,例如上面的代码我们可以设置信号量为2。可以开辟2个线程进行处理。

面试题分析

1、

    dispatch_queue_t queue = dispatch_queue_create("liujilou", DISPATCH_QUEUE_SERIAL);
    NSLog(@"1");
    dispatch_async(queue, ^{
        NSLog(@"2");
        dispatch_sync(queue, ^{
            NSLog(@"3");
        });
        //        NSLog(@"4");
    });
    NSLog(@"5");

死锁。1 5 2 正常打印执行。接下来是一个同步,同步的话会阻塞线程,去执行。但是这个同步块后面放的是一个 4,后面是3。4的执行需要等同步的块执行完,块里面有一个任务3 ,又往队里添加了一个任务3。block块完成就是执行3。由于是同步串行,不会开辟新的线程,先进先出原则所以 4需要等待块执行,块需执行3,但是3又需要等待4执行,就造成了死锁。
     即便是注释掉4 任务仍然存在死锁。为什么内,因为同步函数 先加入到队列,任务3后加入到队列。任务3需要等待同步函数执行完才能执行,但是同步函数有需要任务3执行完才算执行完成。有形成了相互等待。
 

2、

dispatch_queue_t queue = dispatch_queue_create("liujilou", DISPATCH_QUEUE_CONCURRENT);
    NSLog(@"1");
    dispatch_async(queue, ^{
        NSLog(@"2");
        dispatch_sync(queue, ^{
            NSLog(@"3");
        });
        NSLog(@"4");
    });
    NSLog(@"5");
    /*
     2020-03-14 01:18:15.206466+0800 filedome[59905:1360188] 1
     2020-03-14 01:18:15.206720+0800 filedome[59905:1360188] 5
     2020-03-14 01:18:15.206845+0800 filedome[59905:1360270] 2
     2020-03-14 01:18:15.206947+0800 filedome[59905:1360270] 3
     2020-03-14 01:18:15.207040+0800 filedome[59905:1360270] 4
     */

     代码编译从上到下顺序执行,先执行1,然后是异步函数不会阻塞线程同时会耗时所以先执行5,然后执行2。接下来是一个同步会阻塞线程,所以要先执行3,才能执行4. 

3、

dispatch_queue_t queue = dispatch_queue_create("liujilou", DISPATCH_QUEUE_CONCURRENT);
    NSLog(@"1");
    dispatch_async(queue, ^{
        NSLog(@"2");
        dispatch_async(queue, ^{
            NSLog(@"3");
        });
        NSLog(@"4");
    });
    NSLog(@"5");
    /*
     2020-03-14 01:19:52.246409+0800 filedome[59934:1361575] 1
     2020-03-14 01:19:52.246576+0800 filedome[59934:1361575] 5
     2020-03-14 01:19:52.246611+0800 filedome[59934:1361843] 2
     2020-03-14 01:19:52.246703+0800 filedome[59934:1361843] 4
     2020-03-14 01:19:52.246717+0800 filedome[59934:1361634] 3
     */

     代码编译从上到下顺序执行,先执行1,然后是异步函数不会阻塞线程同时会耗时所以先执行5,然后执行2。接下来又是一个异步 同理先执行了4再执行3.

4、

dispatch_queue_t queue = dispatch_queue_create("com.liujilou.queue", DISPATCH_QUEUE_CONCURRENT);
    dispatch_async(queue, ^{
        NSLog(@"1");
    });
    dispatch_async(queue, ^{
        NSLog(@"2");
    });
    dispatch_sync(queue, ^{
        NSLog(@"3");
    });
    NSLog(@"0");
    dispatch_async(queue, ^{
        NSLog(@"7");
    });
    dispatch_async(queue, ^{
        NSLog(@"8");
    });
    dispatch_async(queue, ^{
        NSLog(@"9");
    });
    /*
     2020-03-14 01:27:51.772658+0800 filedome[60051:1366298] 3
     2020-03-14 01:27:51.772671+0800 filedome[60051:1366397] 1
     2020-03-14 01:27:51.772676+0800 filedome[60051:1366395] 2
     2020-03-14 01:27:51.772784+0800 filedome[60051:1366298] 0
     2020-03-14 01:27:51.772906+0800 filedome[60051:1366396] 9
     2020-03-14 01:27:51.772889+0800 filedome[60051:1366395] 7
     2020-03-14 01:27:51.772920+0800 filedome[60051:1366397] 8
     */

     这个的结果会有多种,但是有几个值的先后顺序是固定的。3是同步所以后面的0 需要等待3执行完才执行,后面的是三个异步需要等0执行完再执行。
     1 2 3 这个三个顺序不一定, 3 0 顺序固定。 7 8 9 顺序不一定,但是一定在0之后。多以只要满足这些条件的均正确。
  

发布了83 篇原创文章 · 获赞 12 · 访问量 18万+

猜你喜欢

转载自blog.csdn.net/shengdaVolleyball/article/details/104880766