【iOS开发- GCD】

news/2024/7/20 21:37:09 标签: ios, objective-c, java

前言

GCD的API有很多,学习了比较重要的API,这里学习GCD的实现

GCD是异步执行任务的技术之一,GCD规定我们只需要定义想执行的任务并且追加到适当的Dispatch Queue里,GCD就能生成必要的线程并计划执行任务。

任务和队列

回顾一下任务和队列

学习GCD之前,先来了解 GCD 中两个核心概念:『任务』 和 『队列』。

任务

就是执行操作的意思,在线程中执行的那段代码。在 GCD 中是放在 block 中的。执行任务有两种方式:『同步执行』 和 『异步执行』。两者的主要区别是:是否等待队列的任务执行结束,以及是否具备开启新线程的能力

同步执行和异步执行

  • 同步执行sync
    • 同步添加任务到指定的队列中,在添加的任务执行结束之前,会一直等待,直到队列里面的任务完成之后再继续执行。
    • 只能在当前线程中执行任务,不具备开启新线程的能力。
  • 异步执行async
    • 异步添加任务到指定的队列中,它不会做任何等待,可以继续执行任务。
    • 可以在新的线程中执行任务,具备开启新线程的能力。
    • 异步执行(async)虽然具有开启新线程的能力,但是并不一定开启新线程。这跟任务所指定的队列类型有关。

队列

队列(Dispatch Queue):这里的队列指执行任务的等待队列,即用来存放任务的队列。队列是一种特殊的线性表,采用 FIFO(先进先出)的原则,即新任务总是被插入到队列的末尾,而读取任务的时候总是从队列的头部开始读取。每读取一个任务,则从队列中释放一个任务
在这里插入图片描述
串行队列和并发队列

  • 串行队列
    每次只有一个任务被执行。让任务一个接着一个地执行。(只开启一个线程,一个任务执行完毕后,再执行下一个任务
    在这里插入图片描述

  • 并发队列
    可以让多个任务并发(同时)执行。(可以开启多个线程,并且同时执行任务)
    并发队列只有在异步执行dispatch_async方法下有效
    在这里插入图片描述

任务的创建方法

GCD 提供了同步执行任务的创建方法 dispatch_sync 和异步执行任务创建方法 dispatch_async。

// 串行队列的创建方法
        dispatch_queue_t serialQueue = dispatch_queue_create("testQueue", DISPATCH_QUEUE_SERIAL);
        // 并发队列的创建方法
        dispatch_queue_t concurrentQueue = dispatch_queue_create("testQueue", DISPATCH_QUEUE_CONCURRENT);

不同的组合

由于上述的不同队列和任务的创建,加上之前的主队列和全局队列,对于全局队列我们可以理解为普通的队列,所以只需要对主队列进行研究这样我们出现了6种研究方法

  • 同步执行 + 并发队列
  • 异步执行 + 并发队列
  • 同步执行 + 串行队列
  • 异步执行 + 串行队列
  • 同步执行 + 主队列
  • 异步执行 + 主队列
    请添加图片描述
    『主线程』 中调用 『主队列』+『同步执行』 会导致死锁问题。
  • 这是因为 主队列中追加的同步任务 和 主线程本身的任务 两者之间相互等待,阻塞了 『主队列』,最终造成了主队列所在的线程(主线程)死锁问题
  • 而如果我们在 『其他线程』 调用 『主队列』+『同步执行』,则不会阻塞 『主队列』,自然也不会造成死锁问题。最终的结果是:不会开启新线程,串行执行任务。

队列嵌套情况下,不同组合方式区别

除了上边提到的『主线程』中调用『主队列』+『同步执行』会导致死锁问题。实际在使用『串行队列』的时候,也可能出现阻塞『串行队列』所在线程的情况发生,从而造成死锁问题。这种情况多见于同一个串行队列的嵌套使用。

比如下面代码这样:在『异步执行』+『串行队列』的任务中,又嵌套了『当前的串行队列』,然后进行『同步执行』。

- (void)lock {
    dispatch_queue_t queue = dispatch_queue_create("test.queue", DISPATCH_QUEUE_SERIAL);
    dispatch_async(queue, ^{    // 异步执行 + 串行队列
        dispatch_sync(queue, ^{  // 同步执行 + 当前串行队列
            // 追加任务 1
            [NSThread sleepForTimeInterval:2];              // 模拟耗时操作
            NSLog(@"1---%@",[NSThread currentThread]);      // 打印当前线程
        });
    });

}

解释:执行上面的代码会导致 串行队列中追加的任务 和 串行队列中原有的任务 两者之间相互等待,阻塞了**『串行队列』**,最终造成了 串行队列所在的线程(子线程)死锁问题
主队列造成死锁也是基于这个原因,所以,这也进一步说明了主队列其实并不特殊

对于队列和任务,这里简单的总结。

  • 同步异步任务可以看成是线程问题,异步就是系统给你开了多个门,让你的人(串行或者并行)通过
  • 串行就是一堆人排成一队,而并行就理解为一堆人分成多个队伍进入门(线程,任务)。

『不同队列』+『不同任务』 组合,以及 『队列中嵌套队列』 使用的区别:
请添加图片描述
『异步执行 + 并发队列』就是:系统开启了多个线程(主线程+其他子线程),任务可以多个同时运行
『同步执行 + 并发队列』就是:系统只默认开启了一个主线程,没有开启子线程,虽然任务处于并发队列中,但也只能一个接一个执行了。

1. 同步执行 + 并发队列

  • 在当前线程中执行任务,不会开启新线程,执行完一个任务,再执行下一个任务。
/**
 * 同步执行 + 并发队列
 * 特点:在当前线程中执行任务,不会开启新线程,执行完一个任务,再执行下一个任务。
 */
- (void)syncConcurrent {
    NSLog(@"currentThread---%@",[NSThread currentThread]);  // 打印当前线程
    NSLog(@"syncConcurrent---begin");
    
    dispatch_queue_t queue = dispatch_queue_create("net.bujige.testQueue", DISPATCH_QUEUE_CONCURRENT);
    
    dispatch_sync(queue, ^{
        // 追加任务 1
        [NSThread sleepForTimeInterval:2];              // 模拟耗时操作
        NSLog(@"1---%@",[NSThread currentThread]);      // 打印当前线程
    });
    
    dispatch_sync(queue, ^{
        // 追加任务 2
        [NSThread sleepForTimeInterval:2];              // 模拟耗时操作
        NSLog(@"2---%@",[NSThread currentThread]);      // 打印当前线程
    });
    
    dispatch_sync(queue, ^{
        // 追加任务 3
        [NSThread sleepForTimeInterval:2];              // 模拟耗时操作
        NSLog(@"3---%@",[NSThread currentThread]);      // 打印当前线程
    });
    
    NSLog(@"syncConcurrent---end");
}

请添加图片描述
从 同步执行 + 并发队列 中可看到:

  • 所有任务都是在当前线程(主线程)中执行的,没有开启新的线程(同步执行不具备开启新线程的能力)。
  • 所有任务都在打印的 syncConcurrent—begin 和 syncConcurrent—end 之间执行的(同步任务 需要等待队列的任务执行结束)。
  • 任务按顺序执行的。按顺序执行的原因:虽然 并发队列 可以开启多个线程,并且同时执行多个任务。但是因为本身不能创建新线程,只有当前线程这一个线程(同步任务 不具备开启新线程的能力),所以也就不存在并发。而且当前线程只有等待当前队列中正在执行的任务执行完毕之后,才能继续接着执行下面的操作(同步任务 需要等待队列的任务执行结束)。所以任务只能一个接一个按顺序执行,不能同时被执行。

2. 异步执行 + 并发队列

  • 可以开启多个线程,任务交替(同时)执行。
// 串行队列的创建方法
        dispatch_queue_t serialQueue = dispatch_queue_create("testQueue", DISPATCH_QUEUE_SERIAL);
        // 并发队列的创建方法
        dispatch_queue_t concurrentQueue = dispatch_queue_create("testQueue", DISPATCH_QUEUE_CONCURRENT);

    // 同步执行任务创建方法
    dispatch_sync(serialQueue, ^{
        // 这里放同步执行任务代码
    });
    // 异步执行任务创建方法
    dispatch_async(concurrentQueue, ^{
        // 这里放异步执行任务代码
    });

请添加图片描述
在 异步执行 + 并发队列 中可以看出:

  • 除了当前线程(主线程),系统又开启了 3 个线程,并且任务是交替/同时执行的。(异步执行 具备开启新线程的能力。且 并发队列 可开启多个线程,同时执行多个任务)。
  • 所有任务是在打印的 asyncConcurrent—begin 和 asyncConcurrent—end 之后才执行的。说明当前线程没有等待,而是直接开启了新线程然后一步一步走新线程完成下去的,在新线程中执行任务(异步执行 不做等待,可以继续执行任务)。

3. 同步执行 + 串行队列

  • 不会开启新线程,在当前线程执行任务。任务是串行的,执行完一个任务,再执行下一个任务。.
/**
 * 同步执行 + 串行队列
 * 特点:不会开启新线程,在当前线程执行任务。任务是串行的,执行完一个任务,再执行下一个任务。
 */
- (void)syncSerial {
    NSLog(@"currentThread---%@",[NSThread currentThread]);  // 打印当前线程
    NSLog(@"syncSerial---begin");
    
    dispatch_queue_t queue = dispatch_queue_create("net.bujige.testQueue", DISPATCH_QUEUE_SERIAL);
    
    dispatch_sync(queue, ^{
        // 追加任务 1
        [NSThread sleepForTimeInterval:2];              // 模拟耗时操作
        NSLog(@"1---%@",[NSThread currentThread]);      // 打印当前线程
    });
    dispatch_sync(queue, ^{
        // 追加任务 2
        [NSThread sleepForTimeInterval:2];              // 模拟耗时操作
        NSLog(@"2---%@",[NSThread currentThread]);      // 打印当前线程
    });
    dispatch_sync(queue, ^{
        // 追加任务 3
        [NSThread sleepForTimeInterval:2];              // 模拟耗时操作
        NSLog(@"3---%@",[NSThread currentThread]);      // 打印当前线程
    });
    
    NSLog(@"syncSerial---end");
}

请添加图片描述
在 同步执行 + 串行队列 可以看到:

  • 所有任务都是在当前线程(主线程)中执行的,并没有开启新的线程(同步执行 不具备开启新线程的能力)。
  • 所有任务都在打印的 syncConcurrent—begin 和 syncConcurrent—end 之间执行(同步任务 需要等待队列的任务执行结束)。
  • 任务是按顺序执行的(串行队列 每次只有一个任务被执行,任务一个接一个按顺序执行)

4. 异步执行 + 串行队列

  • 会开启新线程,但是因为任务是串行的,执行完一个任务,再执行下一个任务
/**
 * 异步执行 + 串行队列
 * 特点:会开启新线程,但是因为任务是串行的,执行完一个任务,再执行下一个任务。
 */
- (void)asyncSerial {
    NSLog(@"currentThread---%@",[NSThread currentThread]);  // 打印当前线程
    NSLog(@"asyncSerial---begin");
    
    dispatch_queue_t queue = dispatch_queue_create("net.bujige.testQueue", DISPATCH_QUEUE_SERIAL);
    
    dispatch_async(queue, ^{
        // 追加任务 1
        [NSThread sleepForTimeInterval:2];              // 模拟耗时操作
        NSLog(@"1---%@",[NSThread currentThread]);      // 打印当前线程
    });
    dispatch_async(queue, ^{
        // 追加任务 2
        [NSThread sleepForTimeInterval:2];              // 模拟耗时操作
        NSLog(@"2---%@",[NSThread currentThread]);      // 打印当前线程
    });
    dispatch_async(queue, ^{
        // 追加任务 3
        [NSThread sleepForTimeInterval:2];              // 模拟耗时操作
        NSLog(@"3---%@",[NSThread currentThread]);      // 打印当前线程
    });
    
    NSLog(@"asyncSerial---end");
}


请添加图片描述
在 异步执行 + 串行队列 可以看到:

  • 开启了一条新线程(异步执行 具备开启新线程的能力,串行队列 只开启一个线程)。
  • 所有任务是在打印的 syncConcurrent—begin 和 syncConcurrent—end 之后才开始执行的(异步执行 不会做任何等待,可以继续执行任务)。
  • 任务是按顺序执行的(串行队列 每次只有一个任务被执行,任务一个接一个按顺序执行)。

5. 同步执行 + 主队列

主队列:GCD 默认提供的 串行队列。
默认情况下,平常所写代码是直接放在主队列中的。
所有放在主队列中的任务,都会放到主线程中执行。
可使用 dispatch_get_main_queue() 获得主队列。

  • 同步执行 + 主队列 在不同线程中调用结果也是不一样,在主线程中调用会发生死锁问题,而在其他线程中调用则不会。

5.1 在主线程中调用 『同步执行 + 主队列』

/**
 * 同步执行 + 主队列
 * 特点(主线程调用):互等卡主不执行。
 * 特点(其他线程调用):不会开启新线程,执行完一个任务,再执行下一个任务。
 */
- (void)syncMain {
    
    NSLog(@"currentThread---%@",[NSThread currentThread]);  // 打印当前线程
    NSLog(@"syncMain---begin");
    
    dispatch_queue_t queue = dispatch_get_main_queue();
    
    dispatch_sync(queue, ^{
        // 追加任务 1
        [NSThread sleepForTimeInterval:2];              // 模拟耗时操作
        NSLog(@"1---%@",[NSThread currentThread]);      // 打印当前线程
    });
    
    dispatch_sync(queue, ^{
        // 追加任务 2
        [NSThread sleepForTimeInterval:2];              // 模拟耗时操作
        NSLog(@"2---%@",[NSThread currentThread]);      // 打印当前线程
    });
    
    dispatch_sync(queue, ^{
        // 追加任务 3
        [NSThread sleepForTimeInterval:2];              // 模拟耗时操作
        NSLog(@"3---%@",[NSThread currentThread]);      // 打印当前线程
    });
    
    NSLog(@"syncMain---end");
}

请添加图片描述

  • 打印结果也是截止到了begin,这是因为我们在主线程中执行 syncMain 方法,相当于把 syncMain 任务放到了主线程的队列中。而 同步执行 会等待当前队列中的任务执行完毕,才会接着执行。那么当我们把 任务 1 追加到主队列中,任务 1 就在等待主线程处理完 syncMain 任务。而syncMain 任务需要等待 任务 1 执行完毕,才能接着执行。
  • 那么,现在的情况就是 syncMain 任务和 任务 1 都在等对方执行完毕。这样大家互相等待,所以就卡住了,所以我们的任务执行不了,而且 syncMain也不会end

5.2 在其他线程中调用『同步执行 + 主队列』

要是如果不在主线程中调用,而在其他线程中调用会如何呢?
设计到了创建新线程

  • 使用 NSThread 的 detachNewThreadSelector 方法会创建线程,并自动启动线程执行 selector 任务
- (void)getOtherThread {
    // 使用 NSThread 的 detachNewThreadSelector 方法会创建线程,并自动启动线程执行 selector 任务
    [NSThread detachNewThreadSelector:@selector(syncMain) toTarget:self withObject:nil];

}

请添加图片描述

在其他线程中使用 同步执行 + 主队列 可看到:

  • 所有任务都是在主线程(非当前线程)中执行的,没有开启新的线程(所有放在主队列中的任务,都会放到主线程中执行)。
  • 所有任务都在打印的 syncConcurrent—begin 和 syncConcurrent—end 之间执行(同步任务 需要等待队列的任务执行结束)。
  • 任务是按顺序执行的(主队列是 串行队列,每次只有一个任务被执行,任务一个接一个按顺序执行)。

不同于主线程的原因因为syncMain 任务 放到了其他线程里,而 任务 1、任务 2、任务3 都在追加到主队列中,这三个任务都会在主线程中执行。syncMain 任务 在其他线程中执行到追加 任务 1 到主队列中,因为主队列现在没有正在执行的任务,所以,会直接执行主队列的 任务1,等 任务1 执行完毕,再接着执行 任务 2、任务 3。所以这里不会卡住线程,也就不会造成死锁问题

6. 异步执行 + 主队列

  • 只在主线程中执行任务,执行完一个任务,再执行下一个任务。
/**
 * 异步执行 + 主队列
 * 特点:只在主线程中执行任务,执行完一个任务,再执行下一个任务
 */
- (void)asyncMain {
    NSLog(@"currentThread---%@",[NSThread currentThread]);  // 打印当前线程
    NSLog(@"asyncMain---begin");
    
    dispatch_queue_t queue = dispatch_get_main_queue();
    
    dispatch_async(queue, ^{
        // 追加任务 1
        [NSThread sleepForTimeInterval:2];              // 模拟耗时操作
        NSLog(@"1---%@",[NSThread currentThread]);      // 打印当前线程
    });
    
    dispatch_async(queue, ^{
        // 追加任务 2
        [NSThread sleepForTimeInterval:2];              // 模拟耗时操作
        NSLog(@"2---%@",[NSThread currentThread]);      // 打印当前线程
    });
    
    dispatch_async(queue, ^{
        // 追加任务 3
        [NSThread sleepForTimeInterval:2];              // 模拟耗时操作
        NSLog(@"3---%@",[NSThread currentThread]);      // 打印当前线程
    });
    
    NSLog(@"asyncMain---end");
}


请添加图片描述
在 异步执行 + 主队列 可以看到:

  • 所有任务都是在当前线程(主线程)中执行的,并没有开启新的线程(虽然 异步执行 具备开启线程的能力,但因为是主队列,所以所有任务都在主线程中)。
  • 所有任务是在打印的 syncConcurrent—begin 和 syncConcurrent—end 之后才开始执行的** ( 异步执行不会做任何等待,可以继续执行任务 **
  • 任务是按顺序执行的(因为主队列是 串行队列,每次只有一个任务被执行,任务一个接一个按顺序执行)。

GCD 线程间的通信

根据GCD的需求,我们一般把比较耗费时间的操作,放在其他的线程里面,而对于UI界面的刷新,点击等响应事件则是主线程进行的操作

而当我们有时候在其他线程完成了耗时操作时,需要回到主线程,那么就用到了线程之间的通讯。

/**
 * 线程间通信
 */
- (void)communication {
    // 获取全局并发队列
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    // 获取主队列
    dispatch_queue_t mainQueue = dispatch_get_main_queue();
    
    dispatch_async(queue, ^{
        // 异步追加任务 1
        [NSThread sleepForTimeInterval:2];              // 模拟耗时操作
        NSLog(@"1---%@",[NSThread currentThread]);      // 打印当前线程
        
        // 回到主线程
        dispatch_async(mainQueue, ^{
            // 追加在主线程中执行的任务
            [NSThread sleepForTimeInterval:2];              // 模拟耗时操作
            NSLog(@"2---%@",[NSThread currentThread]);      // 打印当前线程
        });
    });
}

总结

GCD的实现和API一样重要,主要解释了如何将串行并行和同步异步任务结合达到提升进程的效果。


http://www.niftyadmin.cn/n/176834.html

相关文章

【华为OD机试 2023最新 】 最多获得的短信条数、云短信平台优惠活动(C++)

文章目录 题目描述输入描述输出描述用例题目解析C++题目描述 某云短信厂商,为庆祝国庆,推出充值优惠活动。 现在给出客户预算,和优惠售价序列,求最多可获得的短信总条数。 输入描述 第一行客户预算M,其中 0 ≤ M ≤ 10^6 第二行给出售价表, P1, P2, … Pn , 其中 1 ≤…

同城跑腿小程序开源版开发

同城跑腿小程序开源版开发 同城跑腿小程序,支持帮取、帮送模式,包含用户端、骑手端、运营后台,支持一键接单/抢单, 为跑腿团队提供技术解决方案,无加密源码,可私有化部署。 功能特性,为你介绍同城跑腿小程…

Linux下的指令(常用的指令,以及案例展示)

目录 一:模式切换图 二:vi和vim相关操作 三:开机、重启用户的登录注销 四:用户管理(切换、添加、删除、查询) 4.1 基本管理 4.2 用户组​ 五:实用指令 5.1 指定运行级别 5.2 找回运行密…

如何聚焦项目最重要目标 提高项目执行力?

1、最重要目标的确认 软件项目中有很多目标都很重要,但只有一两个目标是最重要的。在任何时刻,我们最好的精力都应该集中在一到两个最重要的目标上。 一般最重要的目标具有以下特点:能够给组织带来巨大的变化;受到干系人的高度关注…

Android自定义library上传到JitPack

一、背景最近公司不是太忙,闲的无聊,准备整理下属于自己的library库,想把自己平时用到的库保存起来到JitPack上,用的时候直接依赖添加。下面是我们把library发布到JitPack上去的记录过程。二、项目配置1.版本不同配置方法有些不同…

完整的性能测试学习

前言: 为什么要进行性能测试? 在真实项目商用时,需要大量的用户进行使用,因此需要模拟大量用户的使用场景; 性能测试概念 什么是性能? 性能就是软件质量中的"效率"特性,效率特性又分为时间特性和资源特性; 时间特性:表示系统处理用户请求的响应时间;…

如何创建和编写项目管理计划?

如何创建项目管理计划?项目管理计划不仅定义了项目的交付时间,还定义了交付方式。如果一个文件只包含将要做什么和何时完成,那么它就不是一个真正的项目管理计划。 这可能会令人困惑,因为有许多解释者关于如何创建项目管理计划或遗…

Vue 3.0 渲染函数API

#概览 此更改不会影响 <template> 用户。 以下是更改的简要总结&#xff1a; h 现在全局导入&#xff0c;而不是作为参数传递给渲染函数渲染函数参数更改为在有状态组件和函数组件之间更加一致vnode 现在有一个扁平的 prop 结构 更多信息&#xff0c;请继续阅读&#x…