「OC」GCD的简单运用

「OC」GCD的简单实用

文章目录

  • 「OC」GCD的简单实用
    • 前言
    • 介绍
    • 任务和队列
      • 任务
        • 同步执行(sync)
        • 异步执行(async)
      • 队列
        • **串行队列(Serial Dispatch Queue)**
        • **并发队列(Concurrent Dispatch Queue)**
    • 创建队列的方式
    • 任务和队列的结合
      • 同步串行队列
      • 同步并行队列
      • 异步串行队列
      • 异步并行队列
    • 死锁
      • 情况一:
      • 情况二:
    • GCD之间的通信
    • 栅栏函数
    • GCD队列组
    • GCD延时执行方法
    • 一次性函数
    • GCD 信号量:dispatch_semaphore
    • 参考文章

前言

前一篇博客初次接触了线程这些内容,了解了NSThread的相关内容,今天我们继续学习线程之中GCD的学习

介绍

Grand Central Dispatch(GCD) 是 Apple 开发的一个多核编程的较新的解决方法。它主要用于优化应用程序以支持多核处理器以及其他对称多处理系统。它是一个在线程池模式的基础上执行的并发任务。在 Mac OS X 10.6 雪豹中首次推出,也可在 iOS 4 及以上版本使用。

GCD是一个替代诸如NSThread等技术的很高效和强大的技术。GCD完全可以处理诸如数据锁定和资源泄漏等复杂的异步编程问题。GCD的工作原理是让一个程序,根据可用的处理资源,安排他们在任何可用的处理器核心上平行排队执行特定的任务。这个任务可以是一个功能或者一个程序段。

——百度百科

  • GCD 可用于多核的并行运算
  • GCD 会自动利用更多的 CPU 内核 (比如双核、四核)
  • GCD 会自动管理线程的生命周期(创建线程、请度任务、销段线程)

任务和队列

在了解GCD之前我们需要任务和队列的两个概念

任务

简单来说就是我们放进GCD的block之中的代码操作

同步执行(sync)
  • 同步添加任务到指定的队列中,在添加的任务执行结束之前,会一直等待,直到队列里面的任务完成之后再继续执行。
  • 只能在当前线程中执行任务,不具备开启新线程的能力
dispatch_sync(queue, ^{
// 这里放同步执行任务代码
});
异步执行(async)
  • 异步添加任务到指定的队列中,它不会做任何等待,可以继续执行任务。
  • 可以在新的线程中执行任务,具备开启新线程的能力。
dispatch_async(queue, ^{
// 这里放同步执行任务代码
});

队列

队列作为一种数据结构,具有先进先出的特点,这里的队列是用于对任务进行存储的,即新任务总是被插入到队列的末尾,而读取任务的时候总是从队列的头部开始读取。每读取一个任务,则从队列中释放一个任务

串行队列(Serial Dispatch Queue)
  • 每次只有一个任务被执行。让任务一个接着一个地执行。(只开启一个线程,一个任务执行完毕后,再执行下一个任务)
并发队列(Concurrent Dispatch Queue)
  • 可以让多个任务并发(同时)执行。(可以开启多个线程,并且同时执行任务)

创建队列的方式

可以使用dispatch_queue_create来创建队列,需要传入两个参数,第一个参数表示队列的唯一标识符,也就是队列的名字,用于 DEBUG,可为空,Dispatch Queue 的名称推荐使用应用程序 ID 这种逆序全程域名;第二个参数用来识别是串行队列还是并发队列。DISPATCH_QUEUE_SERIAL 表示串行队列,DISPATCH_QUEUE_CONCURRENT表示并发队列。

// 串行队列的创建方法
dispatch_queue_t queue = dispatch_queue_create("net.bb.testQueue", DISPATCH_QUEUE_SERIAL);
// 并发队列的创建方法
dispatch_queue_t queue = dispatch_queue_create("net.bb.testQueue", DISPATCH_QUEUE_CONCURRENT);

OC之中还提供一个方法能够让我们获得一个主串行队列,即这个队列的内容都会在主线程之中进行操作,我们可以用一下方法获得主线程

// 主队列的获取方法
dispatch_queue_t queue = dispatch_get_main_queue();

在创建主串行队列之外,OC还另外提供了一个全局并发队列,我们可以使用一下方法

// 获取默认的全局并发队列
dispatch_queue_t globalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

创建全局并发队列的时候,第一个参数是关于这个队列的优先级,第二个参数通常用于指定该队列的附加特性,在一般的使用之中我们都使用默认值0即可。

 // 获取用户交互优先级的全局并发队列
dispatch_queue_t userInteractiveQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0);

任务和队列的结合

接下来我们将任务和队列进行组合使用,来探究GCD的具体用法

同步串行队列

dispatch_queue_t queue = dispatch_queue_create("test", DISPATCH_QUEUE_SERIAL);
dispatch_sync(queue, ^{// 追加任务1for (int i = 0; i < 2; ++i) {NSLog(@"1--%d---%@",i ,[NSThread currentThread]);}
});dispatch_sync(queue, ^{// 追加任务2for (int i = 0; i < 2; ++i) {NSLog(@"2--%d---%@", i, [NSThread currentThread]);}
});

在这里插入图片描述

由于是在主线程之中进行同步操作,所以代码内容是按序执行的,不会开启新的任务

同步并行队列

dispatch_queue_t queue = dispatch_queue_create("testQueue", DISPATCH_QUEUE_CONCURRENT);dispatch_sync(queue, ^{// 追加任务1for (int i = 0; i < 2; ++i) {NSLog(@"1--%d---%@",i ,[NSThread currentThread]);}});dispatch_sync(queue, ^{// 追加任务2for (int i = 0; i < 2; ++i) {NSLog(@"2--%d---%@", i, [NSThread currentThread]);}});

这个在主线程之中使用并行操作,我们可以看到即使我们使用的并发队列,我们的任务依然是按序进行,在主线程之上执行,并不会开启新的线程

在这里插入图片描述

异步串行队列

dispatch_queue_t queue = dispatch_queue_create("testQueue", DISPATCH_QUEUE_CONCURRENT);dispatch_async(queue, ^{// 追加任务1for (int i = 0; i < 2; ++i) {NSLog(@"1--%d---%@",i ,[NSThread currentThread]);}});dispatch_async(queue, ^{// 追加任务2for (int i = 0; i < 2; ++i) {NSLog(@"2--%d---%@", i, [NSThread currentThread]);}});

由于我们使用了异步执行,所以这个两个任务不会在主线程进行,会新建一个线程,在这个新建成的线程进行串行操作。

image-20241007115428602

异步并行队列

NSLog(@"主线程:%@",[NSThread currentThread]);
dispatch_queue_t queue = dispatch_queue_create("test", DISPATCH_QUEUE_CONCURRENT);
ispatch_async(queue, ^{// 追加任务1for (int i = 0; i < 2; ++i) {NSLog(@"1--%d---%@",i ,[NSThread currentThread]);}});dispatch_async(queue, ^{// 追加任务2for (int i = 0; i < 2; ++i) {NSLog(@"2--%d---%@", i, [NSThread currentThread]);}});

可以看到我们的程序就会实现很明显的并发效果

image-20241007120754538

死锁

情况一:

关于GCD同步和异步的使用,有时候会产生死锁。首先我们要明白,同步时,我们必须完成会阻塞当前函数的返回,而异步函数会立即返回执行GCD内容下面的代码】

以下面的代码为例子:

NSLog(@"执行任务1");
dispatch_queue_t queue = dispatch_get_main_queue();
dispatch_sync(queue, ^{NSLog(@"执行任务2");
});NSLog(@"执行任务3");

在我们使用同步队列中把任务2添加到了主队列之中,而由于串行队列需要按序完成任务,我们理清一下:

  1. 主线程:在主线程之中我们的处理流程是这样的——任务1,sync,任务3
  2. 主队列:而在主队列之中我们需要先完成viewDidLoad的方法,再去完成队列之中的后一个任务

在这里插入图片描述
从上图我们就可以看到,想要完成主线程的任务需要按序完成sync之中的内容,而在主队列的要求之中我们需要将viewDidLoad的方法内容进行完成才会接着处理sync之中的内容,于是程序在互相等待之中得不到结果于是就形成了死锁

那我们如何进行修改呢,我们可以将同步的sync修改为异步完成的async,这样子我们的程序就不会要求队列之中的内容要直接完成,可以先执行后面的内容。

NSLog(@"执行任务1");dispatch_queue_t queue = dispatch_get_main_queue();dispatch_async(queue, ^{NSLog(@"执行任务2");});NSLog(@"执行任务3");// dispatch_async不要求立马在当前线程同步执行任务

情况二:

接下来我们将内容变得更加复杂一点:

NSLog(@"执行任务1");dispatch_queue_t queue = dispatch_queue_create("myqueu", DISPATCH_QUEUE_SERIAL);
dispatch_async(queue, ^{ // 0NSLog(@"执行任务2");dispatch_sync(queue, ^{ // 1NSLog(@"执行任务3");});NSLog(@"执行任务4");
});NSLog(@"执行任务5");

以上的内容我们就可以看出,这个程序仍然造成了死锁,执行完2后(2当前线程为串行),打印3任务通过同步任务插入到串行队列,放在打印4的后面(在执行2串行任务里,4的打印在3的前面),但是同步任务有需要先执行3在执行4,就造成相互等待,造成死锁。

我们可以直接将串行队列直接改为并行队列,这样子任务3和任务4就不会产生死锁,大致的输出如下:

  1. 执行任务1
  2. 执行任务5
  3. 执行任务2
  4. 执行任务3(可能在任务 4 之前或之后)
  5. 执行任务4

GCD之间的通信

栅栏函数

栅栏函数可以控制任务执行的顺序,栅栏函数之前的执行完毕之后,执行栅栏函数,然后在执行栅栏函数之后的

 dispatch_queue_t queue = dispatch_queue_create("com.xxccqueue", DISPATCH_QUEUE_CONCURRENT);dispatch_async(queue, ^{for (NSInteger i = 0; i<3; i++) {NSLog(@"%zd-download1--%@",i,[NSThread currentThread]);}});dispatch_async(queue, ^{for (NSInteger i = 0; i<3; i++) {NSLog(@"%zd-download2--%@",i,[NSThread currentThread]);}});//栅栏函数dispatch_barrier_async(queue, ^{NSLog(@"我是一个栅栏函数");[NSThread sleepForTimeInterval:2]; });dispatch_async(queue, ^{for (NSInteger i = 0; i<3; i++) {NSLog(@"%zd-download3--%@",i,[NSThread currentThread]);}});dispatch_async(queue, ^{for (NSInteger i = 0; i<3; i++) {NSLog(@"%zd-download4--%@",i,[NSThread currentThread]);}});

我们可以看到在并发的情况下,在栅栏方法之中我们暂时把异步的操作转化为同步的操作,所以会像栅栏一样将前后的线程任务给分开,我们可以使得线程转化变得更加明显,在栅栏函数处进行线程睡眠来看一下打印出来的时间。
image-20241008215924873

GCD队列组

在我们同时进行异步耗时的操作的时候,当我们有多个耗时操作,我们可以使用GCD之中的队列组来进行操作,我先给出示例在进行讲解

dispatch_group_t group = dispatch_group_create();// 假设我们有三个异步任务
for (int i = 0; i < 3; i++) {dispatch_group_enter(group);dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{// 模拟一个耗时操作[NSThread sleepForTimeInterval:1.0];NSLog(@"任务 %d ", i);// 完成任务后离开组dispatch_group_leave(group);});
}// 在所有任务完成后执行某个操作
dispatch_group_notify(group, dispatch_get_main_queue(), ^{NSLog(@"所有任务都完成");
});
  • dispatch_group_enter 标志着一个任务追加到 group,执行一次,相当于 group 中未执行完毕任务数+1
  • dispatch_group_leave 标志着一个任务离开了 group,执行一次,相当于 group 中未执行完毕任务数-1
  • 当 group 中未执行完毕任务数为0的时候,才会使dispatch_group_wait解除阻塞,以及执行追加到dispatch_group_notify中的任务。
  • dispatch_group_notify在所有任务完成的时候执行其中的任务

GCD延时执行方法

在我们实际运用之中可能会需要使用到,在指定时间之后开始执行某一个任务,这个功能我们可以使用GCD之中的dispatch_after来进行实现。

以下是一个延时两秒的GCD示例:

dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{//执行的任务});

要注意的是:这个dispatch_after并不是先添加到主队列之中然后在指定时间后进行执行,而是在指定时间之后再添加至主队列之中,所以严格来说这个时间不是绝对准确的。

一次性函数

在使用单例的时候,为了预防多线程操作单例的重复创建,我们就会使用一次性函数,这个函数在整个程序运行的时候只会执行一次,在这个函数里面默认是线程安全的

static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{// 只执行1次的代码(这里面默认是线程安全的)
});

GCD 信号量:dispatch_semaphore

这个内容有点类似引用计数,我们可以用这个信号量的功能来限制同时执行的任务数量

创建信号量:

dispatch_semaphore_t semaphore = dispatch_semaphore_create(1); // 初始值为1

等待信号量: 使用 dispatch_semaphore_wait() 来请求信号量。如果信号量的计数大于0,则计数减1并继续执行;如果为0,则当前线程会被阻塞,直到信号量可用。

释放信号量: 使用 dispatch_semaphore_signal() 来释放信号量,将计数加1。

信号量的初始值通常是1,表示一个资源可以被访问;可以设置更高的值来允许多个线程同时访问。

在使用 dispatch_semaphore_wait() 时,如果信号量的计数为0,线程会被阻塞,直到信号量可用或超时。

必须确保每次调用 dispatch_semaphore_wait() 后都有相应的 dispatch_semaphore_signal(),否则可能导致死锁。

参考文章

GCD介绍
iOS多线程-概念

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.xdnf.cn/news/1559723.html

如若内容造成侵权/违法违规/事实不符,请联系一条长河网进行投诉反馈,一经查实,立即删除!

相关文章

从零学编程-C语言-第17天

今天是学习C语言的第17天 时间&#xff1a;2024/10/6 21:16分 使用编译器&#xff1a;vs2019 此贴记录自己的成长 今天学习内容如下 1.自定义类型-结构体 结构体 枚举 联合 //结构体 struct stu {char name[20]; }s1, s2; 这里是全局变量 int main() {struct stu s1,s2 …

AI不应只是简单地替代HR工作

AI在HR领域的应用不是新鲜事。 AI面试官、AI简历分析……许多现有的HR AI专注于自动化日常工作&#xff0c;依赖技术解决问题&#xff0c;因此应用集中在招聘和入职阶段&#xff0c;如简历筛选、候选人筛选和初步面试。 这确实有助于提高重复性工作的效率&#xff0c;也能节约企…

C++内部类和匿名对象

1.内部类 内部类就是定义在一个类的内部的类&#xff0c;比如 #include<iostream> using namespace std; class AA{ public:class BB {public:void print(const AA& A){cout << A.aa1 << " " << A.aa2 << endl;}private:}; priv…

Halcon OCR字符识别

create_text_model_reader创建一个文本模型描述要用于分割的文本find_text. 的parameter value文本分段方法的值为自动和手动. 通常&#xff0c;参数模式应设置为自动因为这种模式更稳定&#xff0c;需要更少 配置工作量。请注意&#xff0c;在这种情况下&#xff0c;还有一个…

tp6发送邮件功能如何实现?tp6的使用教程?

tp6发送邮件有哪些技巧&#xff1f;tp6发送邮件步骤与设置指南&#xff1f; tp6&#xff08;ThinkPHP 6&#xff09;作为一款流行的PHP框架&#xff0c;提供了强大的邮件发送功能&#xff0c;使得开发者能够轻松实现这一需求。AokSend将详细介绍如何在tp6中实现邮件发送功能。…

2024-10-10 问AI: [AI面试题]激活函数在神经网络中的作用是什么?

文心一言 激活函数在神经网络中扮演着至关重要的角色。它们的主要作用包括&#xff1a; 引入非线性&#xff1a; 神经网络中的每一层通常是由线性变换&#xff08;如权重矩阵乘以输入向量再加上偏置&#xff09;构成的。如果没有激活函数&#xff0c;多层神经网络将仅仅是一个…

google标签/注册页面/登录页面

1.怎么制作如下谷歌标签&#xff1f; HTML_code: <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><title>D…

扭矩在各行各业的应用_SunTorque

【大家好&#xff0c;我是唐Sun&#xff0c;唐Sun的唐&#xff0c;唐Sun的Sun。一站式数智工厂解决方案服务商】 扭矩在众多行业中都有着广泛且重要的应用&#xff0c;以下为您列举一些&#xff1a; 在汽车行业&#xff0c;扭矩是衡量发动机性能的关键指标之一。发动机输出的扭…

Unity网络开发基础

概述 基础知识 网络开发必备理论 网络基本概念 IP、端口、Mac地址 客户端和服务器 数据通信模型 网络协议 网络协议概述 OSI模型 TCP/IP协议 TCP/IP协议 TCP和UDP 网络通信 网络游戏通信方案概述 通信前的必备知识 IP地址和端口类 域名解析 序列化和反序列化2进制 概述 字符编码…

linux 搭建sentinel

1.下载 linux执行下面的命令下载包 wget https://github.com/alibaba/Sentinel/releases/download/1.8.6/sentinel-dashboard-1.8.6.jar2.启动 nohup java -Dserver.port9090 -Dcsp.sentinel.dashboard.serverlocalhost:9090 -Dproject.namesentinel-dashboard -jar sentin…

【免费可视化仪表盘】轻松实现静态/动态数据可视化图表—积木仪表盘

在当今信息爆炸的时代&#xff0c;如何从海量数据中快速提取关键信息&#xff0c;实现高效决策&#xff0c;成为了企业和个人面临的重大挑战。而积木仪表盘&#xff0c;就如同一位智慧的导航者&#xff0c;为你轻松开启数据可视化的精彩之旅。 代码下载 https://github.com/je…

elementui+vue 多行数据的合并单元格

多行的合并&#xff0c;可以参考&#xff0c;改改就能用 html <template><el-table :data"students" :span-method"objectSpanMethod"><el-table-column prop"grade" label"年级"></el-table-column><el-…

BMS-绝缘检测

一、为什么要进行绝缘检测 前言&#xff1a;BMS绝缘检测是指对电池组与车体之间的绝缘状态进行实时监测和检测。为了确保电池组与车体之间的绝缘性能良好&#xff0c;防止漏电和短路等安全隐患&#xff0c;BMS绝缘检测系统能够及时发现绝缘故障&#xff0c;并采取相应的措施进…

MySQL 联合索引底层存储结构及索引查找过程解读

前言 大家好&#xff0c;我是 Lorin &#xff0c;联合索引&#xff08;Composite Index&#xff09;又称复合索引&#xff0c;它包括两个或更多列。与单列索引不同&#xff0c;联合索引可以覆盖多个列&#xff0c;这有助于加速复杂查询和过滤条件的检索。联合索引的列顺序非常…

接口测试-day3-jmeter-2组件和元件

组件和元件&#xff1a; 组件&#xff1a;组件指的是jmeter里面任意一个可以使用的功能。比如说查看结果树或者是http请求 元件&#xff1a;元件指是提对组件的分类 组件的作用域&#xff1a;组件放的位置不一样生效也不一样。 作用域取决于组件的的层级结构并不取决于组件的…

GIS前端工程师岗位职责、技术要求和常见面试题

文章来源&#xff1a;https://blog.csdn.net/eqmaster/article/details/141891186 GIS 前端工程师负责运用前端技术实现地理信息系统的可视化交互界面&#xff0c;以提升用户对地理数据的操作体验和分析能力。 GIS 后端工程师岗位职责 界面开发 负责 GIS 应用的前端界面设计…

打造智能洗衣店:Spring Boot订单管理系统

1系统概述 1.1 研究背景 如今互联网高速发展&#xff0c;网络遍布全球&#xff0c;通过互联网发布的消息能快而方便的传播到世界每个角落&#xff0c;并且互联网上能传播的信息也很广&#xff0c;比如文字、图片、声音、视频等。从而&#xff0c;这种种好处使得互联网成了信息传…

Ubuntu安装nvidia显卡驱动

一、安装依赖 1、更新 sudo apt update sudo apt upgrade -y 2、基础工具 sudo apt install -y build-essential cmake 图形界面相关 sudo apt install -y lightdm 注:在弹出对话框选择"lightdm" 下载nvidia驱动&#xff1a; 进入如下网址&#xff1a;http…

机器人末端的负载辨识

关节处的摩擦力变小了&#xff0c;导致系统的参数辨识精度会变高&#xff0c;因为动力学方程中的摩擦力项占的比例会变小。 为什么要有一个负载的参数辨识&#xff0c;因为对于整个系统来说&#xff0c;除了负载哈&#xff0c;其他关节都是不变的&#xff0c;出厂时都设置好了&…

Java基础-知识点

文章目录 数据类型包装类型缓存池 String概述不可变的含义不可变的好处String、StringBuffer、StringBuilderString.intern() 运算参数传递float与double隐式类型转换switch 继承访问权限抽象类与接口super重写与重载**1. 重写(Override)****2. 重载(Overload)** Object类的通用…