23.iOS底层学习之GCD函数和队列

2022-01-07  本文已影响0人  牛牛大王奥利给

本章提纲:
1、GCD的介绍
2、函数
3、队列
4、队列与函数的组合使用
5、GCD部分源码解读
6、GCD部分习题

一、GCD的简介

GCD的全称是:Grand Central Dispatch,是纯C语言实现的,是苹果公司为多核的并行运算提出的一套解决方案。

GCD优点:
二、函数

在GCD中,执行任务的有两种方式,分别是:同步函数异步函数

三、队列

队列分为串行队列并发队列。队列是一种数据结构,遵循FIFO(Frist In Frist Out)先进先出的原则。后来的任务会排在队尾,先来的任务会被先执行。

在GCD中,有两个常见的队列,主队列(main)全局并发队列(global)。主队列属于串行队列,而全局队列是并发队列。

全局队列和主队列的日常搭配使用示例:

dispatch_async(dispatch_get_global_queue(0, 0), ^{
    //执行耗时操作,例如网络请求
    dispatch_async(dispatch_get_main_queue(), ^{
        //回到主线程进行UI操作,根据数据更新UI
    });
});
四、队列与函数的组合使用

小结:
只有异步函数的并发队列的时候才会是真正的异步执行,没有顺序。
同步函数或者串行队列都会导致任务最终串行执行,一个挨着一个的执行。

五、GCD部分源码解读

阅读GCD的源码还是相对比较困难的,这份源码的注释比较少,宏定义比较多,纯c语言实现。

libdispatch-1271.120.2源码地址

有了源代码,我们分别来了解一下我们常用的GCD函数底层实现。

dispatch_queue_main_t
dispatch_get_main_queue(void)
{
    return DISPATCH_GLOBAL_OBJECT(dispatch_queue_main_t, _dispatch_main_q);
}

通过上面的dispatch_get_main_queue的实现,我们可以看到,返回的是dispatch_queue_main_t类型。通过方法DISPATCH_GLOBAL_OBJECT来生成最后要返回的内容。而DISPATCH_GLOBAL_OBJECT的实现如下:

#define DISPATCH_GLOBAL_OBJECT(type, object) ((OS_OBJECT_BRIDGE type)&(object))

可以通过上面的代码看到DISPATCH_GLOBAL_OBJECT的最终结果是由typeobject两个参数进行与操作的结果。

第一个参数传入的是dispatch_queue_main_t,这个通过查询源码发现是一个结构体dispatch_queue_static_s指针:

typedef struct dispatch_queue_static_s *dispatch_queue_main_t;

dispatch_queue_static_s的定义如下:

// Cache aligned type for static queues (main queue, manager)
struct dispatch_queue_static_s {
    struct dispatch_lane_s _as_dl[0]; \
    DISPATCH_LANE_CLASS_HEADER(lane);
} DISPATCH_CACHELINE_ALIGN;

根据这上面👆🏻的注释可以了解到,这是为了静态队列(主队列)服务的内存对齐类型。
这是传入的第一个参数。
第二个参数_dispatch_main_q经过搜索可以看到定义如下:

// 6618342 Contact the team that owns the Instrument DTrace probe before
//         renaming this symbol
struct dispatch_queue_static_s _dispatch_main_q = {
    DISPATCH_GLOBAL_OBJECT_HEADER(queue_main),
#if !DISPATCH_USE_RESOLVERS
    .do_targetq = _dispatch_get_default_queue(true),
#endif
    .dq_state = DISPATCH_QUEUE_STATE_INIT_VALUE(1) |
            DISPATCH_QUEUE_ROLE_BASE_ANON,
    .dq_label = "com.apple.main-thread",
    .dq_atomic_flags = DQF_THREAD_BOUND | DQF_WIDTH(1),
    .dq_serialnum = 1,
};

而且我们通过上面的源码关于dispatch_queue_static_s部分,最终返回dispatch_queue_main_t是通过dispatch_lane_s来完成的。

(此部分比较困难,也是摸索着探究,很多地方也看不太懂,先整理一下初步的理解和思路,后续加深理解会更新)

dispatch_queue_global_t
dispatch_get_global_queue(intptr_t priority, uintptr_t flags)
{
    dispatch_assert(countof(_dispatch_root_queues) ==
            DISPATCH_ROOT_QUEUE_COUNT);

    if (flags & ~(unsigned long)DISPATCH_QUEUE_OVERCOMMIT) {
        return DISPATCH_BAD_INPUT;
    }
    dispatch_qos_t qos = _dispatch_qos_from_queue_priority(priority);
#if !HAVE_PTHREAD_WORKQUEUE_QOS
    if (qos == QOS_CLASS_MAINTENANCE) {
        qos = DISPATCH_QOS_BACKGROUND;
    } else if (qos == QOS_CLASS_USER_INTERACTIVE) {
        qos = DISPATCH_QOS_USER_INITIATED;
    }
#endif
    if (qos == DISPATCH_QOS_UNSPECIFIED) {
        return DISPATCH_BAD_INPUT;
    }
    return _dispatch_get_root_queue(qos, flags & DISPATCH_QUEUE_OVERCOMMIT);
}
static inline dispatch_queue_global_t
_dispatch_get_root_queue(dispatch_qos_t qos, bool overcommit)
{
    if (unlikely(qos < DISPATCH_QOS_MIN || qos > DISPATCH_QOS_MAX)) {
        DISPATCH_CLIENT_CRASH(qos, "Corrupted priority");
    }
    return &_dispatch_root_queues[2 * (qos - 1) + overcommit];
}

而_dispatch_root_queues的实现如下:

// 6618342 Contact the team that owns the Instrument DTrace probe before
//         renaming this symbol
struct dispatch_queue_global_s _dispatch_root_queues[] = {
#define _DISPATCH_ROOT_QUEUE_IDX(n, flags) \
        ((flags & DISPATCH_PRIORITY_FLAG_OVERCOMMIT) ? \
        DISPATCH_ROOT_QUEUE_IDX_##n##_QOS_OVERCOMMIT : \
        DISPATCH_ROOT_QUEUE_IDX_##n##_QOS)
#define _DISPATCH_ROOT_QUEUE_ENTRY(n, flags, ...) \
    [_DISPATCH_ROOT_QUEUE_IDX(n, flags)] = { \
        DISPATCH_GLOBAL_OBJECT_HEADER(queue_global), \
        .dq_state = DISPATCH_ROOT_QUEUE_STATE_INIT_VALUE, \
        .do_ctxt = _dispatch_root_queue_ctxt(_DISPATCH_ROOT_QUEUE_IDX(n, flags)), \
        .dq_atomic_flags = DQF_WIDTH(DISPATCH_QUEUE_WIDTH_POOL), \
        .dq_priority = flags | ((flags & DISPATCH_PRIORITY_FLAG_FALLBACK) ? \
                _dispatch_priority_make_fallback(DISPATCH_QOS_##n) : \
                _dispatch_priority_make(DISPATCH_QOS_##n, 0)), \
        __VA_ARGS__ \
    }
    _DISPATCH_ROOT_QUEUE_ENTRY(MAINTENANCE, 0,
        .dq_label = "com.apple.root.maintenance-qos",
        .dq_serialnum = 4,
    ),
    _DISPATCH_ROOT_QUEUE_ENTRY(MAINTENANCE, DISPATCH_PRIORITY_FLAG_OVERCOMMIT,
        .dq_label = "com.apple.root.maintenance-qos.overcommit",
        .dq_serialnum = 5,
    ),
    _DISPATCH_ROOT_QUEUE_ENTRY(BACKGROUND, 0,
        .dq_label = "com.apple.root.background-qos",
        .dq_serialnum = 6,
    ),
    _DISPATCH_ROOT_QUEUE_ENTRY(BACKGROUND, DISPATCH_PRIORITY_FLAG_OVERCOMMIT,
        .dq_label = "com.apple.root.background-qos.overcommit",
        .dq_serialnum = 7,
    ),
    _DISPATCH_ROOT_QUEUE_ENTRY(UTILITY, 0,
        .dq_label = "com.apple.root.utility-qos",
        .dq_serialnum = 8,
    ),
    _DISPATCH_ROOT_QUEUE_ENTRY(UTILITY, DISPATCH_PRIORITY_FLAG_OVERCOMMIT,
        .dq_label = "com.apple.root.utility-qos.overcommit",
        .dq_serialnum = 9,
    ),
    _DISPATCH_ROOT_QUEUE_ENTRY(DEFAULT, DISPATCH_PRIORITY_FLAG_FALLBACK,
        .dq_label = "com.apple.root.default-qos",
        .dq_serialnum = 10,
    ),
    _DISPATCH_ROOT_QUEUE_ENTRY(DEFAULT,
            DISPATCH_PRIORITY_FLAG_FALLBACK | DISPATCH_PRIORITY_FLAG_OVERCOMMIT,
        .dq_label = "com.apple.root.default-qos.overcommit",
        .dq_serialnum = 11,
    ),
    _DISPATCH_ROOT_QUEUE_ENTRY(USER_INITIATED, 0,
        .dq_label = "com.apple.root.user-initiated-qos",
        .dq_serialnum = 12,
    ),
    _DISPATCH_ROOT_QUEUE_ENTRY(USER_INITIATED, DISPATCH_PRIORITY_FLAG_OVERCOMMIT,
        .dq_label = "com.apple.root.user-initiated-qos.overcommit",
        .dq_serialnum = 13,
    ),
    _DISPATCH_ROOT_QUEUE_ENTRY(USER_INTERACTIVE, 0,
        .dq_label = "com.apple.root.user-interactive-qos",
        .dq_serialnum = 14,
    ),
    _DISPATCH_ROOT_QUEUE_ENTRY(USER_INTERACTIVE, DISPATCH_PRIORITY_FLAG_OVERCOMMIT,
        .dq_label = "com.apple.root.user-interactive-qos.overcommit",
        .dq_serialnum = 15,
    ),
};

以上这两种常见的队列源码的实现大致如上。

六、GCD习题解析
- (void)mainSyncTest{
    NSLog(@"0");
    dispatch_sync(dispatch_get_main_queue(), ^{
        NSLog(@"1");
    });
    NSLog(@"2");
}

输出:0,然后死锁
实际运行结果:

image.png
解析:默认是在主队列上,然后同步函数不会额外开启新的线程,也是在主队列上,主队列执行完0,然后准备往下接着执行遇见了自己队列上的同步函数,主队列顺序执行此时应该执行同步函数,但是同步函数又再主队列上 dispatch_sync(dispatch_get_main_queue()),又要接着等自己执行完毕,所以造成了死锁。
也就是说主队列遇见同步函数没问题,但是同步函数又切回到了主队列上,主队列该执行同步函数了,然后同步函数切回主队列这样造成了死锁。
- (void)mainAsyncTest{
    dispatch_async(dispatch_get_main_queue(), ^{
        NSLog(@"1");
    });
    NSLog(@"2");
}

输出:2,1
解析:都在主队列上,异步函数不会阻塞,主队列先遇到异步函数,然后是2,然后是异步函数切回主队列上的1。所以打印是2,1

/**
 全局同步
 全局队列:一个并发队列
 */
- (void)globalSyncTest{
    for (int i = 0; i<20; i++) {
        dispatch_sync(dispatch_get_global_queue(0, 0), ^{
            NSLog(@"%d-%@",i,[NSThread currentThread]);
        });
    }
    NSLog(@"hello queue");
}

输出:0~19顺序输出、然后hello queue
解析:主队列遇见同步任务,等待同步任务内部执行完毕,因为是同步任务所以等待块返回才会执行下一个,所以结果是先顺序输出,然后输出hello queue

/**
 全局异步
 全局队列:一个并发队列
 */
- (void)globalAsyncTest{
    
    for (int i = 0; i<20; i++) {
        dispatch_async(dispatch_get_global_queue(0, 0), ^{
            NSLog(@"%d-%@",i,[NSThread currentThread]);
        });
    }
    NSLog(@"hello queue");
}

输出:hello queue、然后乱序
解析:都在默认的主队列上,然后先遇到异步函数,然后是hello queue,所以先打印hello queue,然后异步函数执行内部的任务,是并发队列,异步任务,不用等待,所以后面乱序。

- (void)textDemo{
    dispatch_queue_t queue = dispatch_queue_create("cooci", DISPATCH_QUEUE_CONCURRENT);
    NSLog(@"1");
    dispatch_async(queue, ^{
        NSLog(@"2");
        dispatch_async(queue, ^{
            NSLog(@"3");
        });
        NSLog(@"4");
    });

    NSLog(@"5");
}

输出:1、5、2、4、3
解析:主队列上是,1、异步块、5,所以先打印1,5;然后异步块中的内容是2、异步块、4;所以再打印2、4,然后最后一个异步块中是3,再打印3。结果是1、5、2、4、3。

练习二

- (void)textDemo1{  
    dispatch_queue_t queue = dispatch_queue_create("cooci", NULL);
    NSLog(@"1");
    dispatch_async(queue, ^{
        NSLog(@"2");
        dispatch_sync(queue, ^{
            NSLog(@"3");
        });
    });
    NSLog(@"5");
}

输出:1、5、2,死锁
实际运行:

image.png
解析:这个队列为NULL是默认是串行队列,主队列上首先是1、异步块、5,所以先打印1、5;然后异步块里面是一个串行队列上的一个2、同步块,2先打印,然后后面的同步块会阻塞队列后面的任务执行,同步块里的内容又等着同步函数执行完毕,同步函数等着队列上的3执行,又形成了死锁。

练习三

- (void)textDemo2{
    // 同步队列
    dispatch_queue_t queue = dispatch_queue_create("cooci", DISPATCH_QUEUE_CONCURRENT);
    NSLog(@"1");
    // 异步函数
    dispatch_async(queue, ^{
        NSLog(@"2");
        // 同步
        dispatch_sync(queue, ^{
            NSLog(@"3");
        });
        NSLog(@"4");
    });
    NSLog(@"5");
}

输出:1、5、2、3、4;
解析:和上面的分析差不多,区别在与同步函数阻塞了以后因为是并发队列,不用等着同步函数了,就先执行了3,然后再4这样。

- (void)wbinterDemo{
    dispatch_queue_t queue = dispatch_queue_create("com.lg.cooci.cn", DISPATCH_QUEUE_SERIAL);

    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");
    });
    // A: 1230789
    // B: 1237890
    // C: 3120798
    // D: 2137890
}

答案:A,这个因为都是异步任务,但是内部都是在串行队列,1,2,3分别按照顺序入队到串行队列queue中,3的地方是同步要等前面的执行完,所以3执行完是0,后面的7,8,9和前面的一样顺序打印。所以出来的是1,2,3,0,7,8,9。

- (void)MTDemo{
    while (self.num < 5) {
        dispatch_async(dispatch_get_global_queue(0, 0), ^{
            self.num++;
        });
    }
    NSLog(@"end : %d",self.num); 
}

答案:大于等于5。因为while是死循环 至少是5以后才会跳出循环,而异步并发的时候self.num不一定被加过几次,反正小于5之前就一直在循环。

- (void)KSDemo{
    for (int i= 0; i<10000; i++) {
        dispatch_async(dispatch_get_global_queue(0, 0), ^{
            self.num++;
        });
    }
    NSLog(@"end : %d",self.num); // 
}

答案:<=10000 ,异步并发队列执行到主队列上没顺序。

上一篇 下一篇

猜你喜欢

热点阅读