iOS高级理论:多线程专题 - (2) GCD信号量的应

一、控制最大并发数

对计算机了解的都会知道信号量的作用,当我们多个线程要访问同一个资源的时候,往往会设置一个信号量,当信号量大于0的时候,新的线程可以去操作这个资源,操作时信号量-1,操作完后信号量+1,当信号量等于0的时候,必须等待,所以通过控制信号量,我们可以控制能够同时进行的并发数。

信号量是一个整数,在创建的时候会有一个初始值,这个初始值往往代表我要控制的同时操作的并发数。在操作中,对信号量会有两种操作:信号通知与等待。信号通知时,信号量会+1,等待时,如果信号量大于0,则会将信号量-1,否则,会等待直到信号量大于0。什么时候会大于零呢?往往是在之前某个操作结束后,我们发出信号通知,让信号量+1。

说完概念,我们来看看GCD中的三个信号量操作:

  • dispatch_semaphore_create:创建一个信号量(semaphore)
  • dispatch_semaphore_signal:信号通知,即让信号量+1
  • dispatch_semaphore_wait:等待,直到信号量大于0时,即可操作,同时将信号量-1

在 Objective-C 中,可以使用 dispatch_semaphore 来控制最大并发数量,限制同时执行的线程数量。通过设置初始值为最大并发数的 dispatch_semaphore,可以实现限制并发执行的效果。

以下是一个示例代码,演示如何使用 dispatch_semaphore 控制最大并发数量:

// 定义最大并发数
#define MAX_CONCURRENT_TASKS 3dispatch_semaphore_t semaphore = dispatch_semaphore_create(MAX_CONCURRENT_TASKS); // 创建信号量,初始值为最大并发数
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);for (int i = 0; i < 10; i++) {dispatch_async(queue, ^{dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER); // 等待信号量NSLog(@"Task %d started", i);sleep(1); // 模拟任务执行NSLog(@"Task %d finished", i);dispatch_semaphore_signal(semaphore); // 释放信号量});
}

在上面的示例中,定义了一个最大并发数为 3 的宏 MAX_CONCURRENT_TASKS。然后创建了一个初始值为 MAX_CONCURRENT_TASKSdispatch_semaphore。在循环中,通过 dispatch_async 在全局队列中执行了 10 个任务,每个任务在开始时调用 dispatch_semaphore_wait 等待信号量,表示占用一个资源;在任务执行完成后调用 dispatch_semaphore_signal 释放信号量,表示释放资源。

上面代码表示我要操作10次,但是控制允许同时并发的操作最多只有3次,当并发量达到3后,信号量就减小到0了,这时候wait操作会起作用,DISPATCH_TIME_FOREVER表示会永远等待,一直等到信号量大于0,也就是有操作完成了,将信号量+1了,这时候才可以结束等待,进行操作,并且将信号量-1,这样新的任务又要等待。

通过这种方式,可以限制同时执行的线程数量为最大并发数,控制并发执行的效果。这种方法适用于需要限制并发数量的场景,例如网络请求、文件读写等操作。

二、控制多个请求结束后统一操作

假设我们一个页面需要同时进行多个请求,他们之间倒是不要求顺序关系,但是要求等他们都请求完毕了再进行界面刷新或者其他什么操作。

这个需求我们一般可以用GCD的group和notify来做到:

dispatch_group_t group = dispatch_group_create();
dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{//请求1NSLog(@"Request_1");
});
dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{//请求2NSLog(@"Request_2");
});
dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{//请求3NSLog(@"Request_3");
});
dispatch_group_notify(group, dispatch_get_main_queue(), ^{//界面刷新NSLog(@"任务均完成,刷新界面");
});

notify的作用就是在group中的其他操作全部完成后,再操作自己的内容,所以我们会看到上面三个内容都打印出来后,才打印界面刷新的内容。

但是当将上面三个操作改成真实的网络操作后,这个简单的做法会变得无效,为什么呢?因为网络请求需要时间,而线程的执行并不会等待请求完成后才真正算作完成,而是只负责将请求发出去,线程就认为自己的任务算完成了,当三个请求都发送出去,就会执行notify中的内容,但请求结果返回的时间是不一定的,也就导致界面都刷新了,请求才返回,这就是无效的。

要解决这个问题,我们就要用到上面说的信号量来操作了。

在每个请求开始之前,我们创建一个信号量,初始为0,在请求操作之后,我们设一个dispatch_semaphore_wait,在请求到结果之后,再将信号量+1,也即是dispatch_semaphore_signal。这样做的目的是保证在请求结果没有返回之前,一直让线程等待在那里,这样一个线程的任务一直在等待,就不会算作完成,notify的内容也就不会执行了,直到每个请求的结果都返回了,线程任务才能够结束,这时候notify也才能够执行。伪代码如下:

dispatch_semaphore_t sema = dispatch_semaphore_create(0);
[网络请求:{成功:dispatch_semaphore_signal(sema);失败:dispatch_semaphore_signal(sema);
}];
dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER);

三、控制多个请求顺序执行

有时候我们需要按照顺序执行多次请求,比如先请求到用户信息,然后根据用户信息中的内容去请求相关的数据,这在平常的代码中直接按照顺序往下写代码就可以了,但这里因为涉及到多线程之间的关系,就叫做线程依赖。

线程依赖用GCD做比较麻烦,建议用NSOperationQueue做,可以更加方便的设置任务之间的依赖。

// 1.任务一:获取用户信息
NSBlockOperation *operation1 = [NSBlockOperation blockOperationWithBlock:^{[self request_A];
}];// 2.任务二:请求相关数据
NSBlockOperation *operation2 = [NSBlockOperation blockOperationWithBlock:^{[self request_B];
}];// 3.设置依赖
[operation2 addDependency:operation1];// 任务二依赖任务一// 4.创建队列并加入任务
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
[queue addOperations:@[operation2, operation1] waitUntilFinished:NO];

一般的多线程操作这样做是可以的,线程2会等待线程1完成后再执行。但是对于网络请求,问题又来了,同样,网络请求需要时间,线程发出请求后即认为任务完成了,并不会等待返回后的操作,这就失去了意义。

要解决这个问题,还是用信号量来控制,其实是一个道理,代码也是一样的,在一个任务操作中:

dispatch_semaphore_t sema = dispatch_semaphore_create(0);
[网络请求:{成功:dispatch_semaphore_signal(sema);失败:dispatch_semaphore_signal(sema);
}];
dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER);

还是去等待请求返回后,才让任务结束。而依赖关系则通过NSOperationQueue来实现。

其实归根结底,中心思想就是通过信号量,来控制线程任务什么时候算作结束,如果不用信号量,请求发出后即认为任务完成,而网络请求又要不同时间,所以会打乱顺序。因此用一个信号量来控制在单个线程操作内,必须等待请求返回,自己要执行的操作完成后,才将信号量+1,这时候一直处于等待的代码也得以执行通过,任务才算作完成。

通过这个方法,就可以解决由于网络请求耗时特性而带来的一些意想不到的多线程处理的问题。

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

第2.6章 StarRocks表设计——数据压缩

注&#xff1a;本篇文章阐述的是StarRocks-3.2版本的数据压缩机制 1.数据压缩概述 StarRocks支持对表&#xff0c;索引数据进行压缩(compression)。数据压缩不仅有助于节省存储空间&#xff0c;还能提高I/O密集型任务的性能&#xff0c;但是压缩和解压数据需要额外的cpu资源。…

【接口加密】接口加密的未来发展与应用场景

目录 3.1 接口加密与区块链技术的结合 3.1.1 区块链技术的安全特性与优势 3.1.2 接口加密在区块链中的应用案例 3.2 接口加密与物联网安全 3.2.1 物联网安全的挑战与需求 3.2.2 接口加密在物联网领域的实际应用 3.3 接口加密在金融与电子商务领域的应用 随着信息技术的不…

【Java网络编程06】HTTPS原理

1. HTTPS基本概念 HTTPS&#xff1a;HTTPS也是一个应用层协议&#xff0c;它在HTTP协议的基础上引入了一个加密层——SSL协议&#xff0c;区别就在于HTTP协议是基于明文传输的&#xff08;不安全&#xff09;&#xff0c;使用HTTPS加密就能在一定程度上防止数据在传输过程中被…

redis架构系列——生产常用的部署模式介绍

主从高可用模式 这是最基本的高可用模式&#xff0c;它允许数据从主节点自动复制到一个或多个从节点。这种模式下&#xff0c;从节点可以处理读操作&#xff0c;从而实现负载均衡&#xff0c;并提供故障恢复的基本功能。然而&#xff0c;它的故障恢复不能自动化&#xff0c;写操…

内核解读之内存管理(8)什么是page cache

文章目录 0. 文件系统的层次结构1.什么是page cache2.感观认识page cache3. Page Cache的优缺点3.1 Page Cache 的优势3.2 Page Cache 的劣势 0. 文件系统的层次结构 在了解page cache之前&#xff0c;我们先看下文件系统的层次结构。 1 VFS 层 VFS &#xff08; Virtual Fi…

Vulhub 靶场训练 DC-8解析

一、环境搭建 kali的IP地址&#xff1a;192.168.200.14 DC-8的IP地址&#xff1a;192.168.200.13&#xff08;一个flag&#xff09; 靶机和攻击机处于同一个网络方式&#xff1a;nat或桥接 若出现开机错误&#xff0c;适当将dc的兼容版本改低&#xff0c;我的vmware workst…

pythonJax小记(三):python: 使用Jax已知若干坐标,提取二维矩阵中对应坐标的值(持续更新,评论区可以补充)

python: 使用Jax已知若干坐标&#xff0c;提取二维矩阵中对应坐标的值 前言直接上代码 前言 自用&#xff0c;刚开始接触可能顺序会比较乱。 直接上代码 import jax.numpy as jnp from jax import jitjit def _extractValues(matrix, positions): values matrix[pos…

Vue事件处理之v-on

1. 使用及定义 定义方法 function 方法名称(接受的event或是什么都不写) {//不管方法后括号内写与不写event,都可以接受到方法内表达式 }//定义一个接受参数的方法,此时也会在传入event function 方法名称(传入参数) {//可接受传入参数与event方法内表达式 } //定义一个接受参…

说一下 JVM 有哪些垃圾回收器?

如果说垃圾收集算法是内存回收的方法论&#xff0c;那么垃圾收集器就是内存回收的具体实现。下图展示了7种作用于不同分代的收集器&#xff0c;其中用于回收新生代的收集器包括Serial、ParNew、Parallel Scavenge&#xff0c;回收老年代的收集器包括SerialOld、Parallel Old、C…

34.云原生之devops集成istio

云原生专栏大纲 文章目录 什么样的项目适合上istio参考bookinfo中reviews服务资源DeploymentServiceGatewayVirtualServiceDestinationRule kustomize资源清单 经过前边的学习我们已经知道istio官方bookinfo应用的部署及流量治理。我们自己的项目cicd发布后如何使用Istio呢&am…

恒创科技:香港服务器和香港云服务器哪个好啊?

"香港服务器"和"香港云服务器"&#xff0c;是两种不同的香港区域的服务器&#xff0c;免备案&#xff0c;都有各自的优势和适用场景&#xff0c;取决于您的需求和预算。以下是它们的一些区别和特点&#xff1a; 香港服务器&#xff1a; 物理服务器&#xf…

第2.5章 StarRocks表设计——行列混存表

注&#xff1a;本篇文章阐述的是StarRocks- 3.2.3版本的行列混存表 一、概述 1.1 背景 StarRocks 基于列存格式引擎构建&#xff0c;在高并发场景&#xff0c;用户希望从系统中获取整行数据。当表宽时&#xff0c;列存格式将放大随机IO和读写。自3.2.3开始&#xff0c;StarRo…

Java/Python/Go不同开发语言基础数据操作总结-基础篇

Java/Python/Go不同开发语言基础数据操作总结-字符篇 1. Java1.1 字符串操作1.1.1 构建String1.1.2 String拆分转列表1.1.4 列表拼接成String1.1.4 寻找第一个匹配的子字符串1.1.5 判断2个字符串是否相同 2. Go2.1 字符串操作2.1.1 构建String2.1.2 String拆分转列表2.1.3 列表…

(delphi11最新学习资料) Object Pascal 学习笔记---第5章第5节(delphi中的指针)

5.5 什么是指针&#xff1f; ​ 指针是 Object Pascal 语言的另一种基本数据类型。一些面向对象的语言在很大程度上隐藏了指针这种强大但危险的语言结构&#xff0c;而 Object Pascal 则允许程序员在需要时使用指针&#xff08;一般情况下并不经常使用&#xff09;。 ​ 那么…

密码算法简单整理

密码组成 国外的密码算法&#xff1a; DES、3DES、AES、SHA1、SHA2、SHA3、DSA、RSA、RC4等 高危密码算法&#xff1a; MD5、DES、RSA1024以下、SSH1.0、SSL3.0以下、SHA1等 密码算法通常可分为三大类&#xff1a; 对称密码算法 非对称密码算法 密码杂凑算法 1.对称密码算法&am…

LeetCode 1637.两点之间不包含任何点的最宽垂直区域

给你 n 个二维平面上的点 points &#xff0c;其中 points[i] [xi, yi] &#xff0c;请你返回两点之间内部不包含任何点的 最宽垂直区域 的宽度。 垂直区域 的定义是固定宽度&#xff0c;而 y 轴上无限延伸的一块区域&#xff08;也就是高度为无穷大&#xff09;。 最宽垂直区…

udp服务器【Linux网络编程】

目录 一、UDP服务器 1、创建套接字 2、绑定套接字 3、运行 1&#xff09;读取数据 2&#xff09;发送数据 二、UDP客户端 创建套接字&#xff1a; 客户端不用手动bind 收发数据 处理消息和网络通信解耦 三、应用场景 1、服务端执行命令 2、Windows上的客户端 3…

掌控互联网脉络:深入解析边界网关协议(BGP)的力量与挑战

BGP简介 边界网关协议&#xff08;Border Gateway Protocol&#xff0c;BGP&#xff09;是互联网上最重要的路由协议之一&#xff0c;负责在不同自治系统&#xff08;AS&#xff09;之间传播路由信息。BGP使得互联网中的不同网络可以互相通信&#xff0c;支持互联网的规模化扩…

2278. 企鹅游行(最大流,拆点)

活动 - AcWing 在南极附近的某个地方&#xff0c;一些企鹅正站在一些浮冰上。 作为群居动物&#xff0c;企鹅们喜欢聚在一起&#xff0c;因此&#xff0c;它们想在同一块浮冰上会合。 企鹅们不想淋湿自己&#xff0c;所以它们只能利用自己有限的跳跃能力&#xff0c;在一块块…

UnityWebGL 设置全屏

这是Unity导出Web默认打开的页面尺寸 修改后效果 修改 index.html 文件 1.div元素的id属性值为"unity-container"&#xff0c;宽度和高度都设置为100%&#xff0c;意味着该div元素将占据整个父容器的空间。canvas元素的id属性值为"unity-canvas"&#xff…