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

news/2024/7/20 21:22:30 标签: ios

一、控制最大并发数

对计算机了解的都会知道信号量的作用,当我们多个线程要访问同一个资源的时候,往往会设置一个信号量,当信号量大于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 3

dispatch_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), ^{
    //请求1
    NSLog(@"Request_1");
});
dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
    //请求2
    NSLog(@"Request_2");
});
dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
    //请求3
    NSLog(@"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.niftyadmin.cn/n/5391368.html

相关文章

Spring中实现策略模式示例

Spring中实现策略模式示例 在本教程中&#xff0c;将探索 Spring 框架中的各种策略模式实现&#xff0c;例如列表注入、映射注入和方法注入。 什么是策略模式&#xff1f; 策略模式是一种设计原则&#xff0c;允许您在运行时切换不同的算法或行为。它允许您在不改变应用程序核…

Windows安装PHP及在VScode中配置插件,使用PHP输出HelloWorld

安装PHP PHP官网下载地址(8.3版本)&#xff1a;PHP For Windows&#xff1a;二进制文件和源代码发布 点击下载.zip格式压缩包&#xff1a; 历史版本在Old archives中下载。推荐在Documentation download中下载官方文档&#xff0c;方便学习。 下载完成后在一个顺眼的地方解压压…

ES6 字符串面试题

如何判断字符串 “Hello, World!” 是否以 “Hello” 开头&#xff1f; 答案&#xff1a; const str "Hello, World!"; const startsWithHello str.startsWith("Hello"); console.log(startsWithHello); // 输出 true如何判断字符串 “Hello, World!” …

eclipse中open Type 、 open type in Hierachy、open Resource的区别

目录 场景&#xff1a; open Type open Resource open type in Hierachy 场景&#xff1a; 在项目中想要研究底层代码&#xff0c;经常要用eclipse看依赖jar包的类&#xff0c;比如spring的源码中AbstractApplicationContext类CTLSHIFTT用的少&#xff0c;经常用的CTLSHIR…

[word] word竖排文字时,如何让英文和数字也纵向显示 #笔记#经验分享

word竖排文字时&#xff0c;如何让英文和数字也纵向显示 用Word进行排版成为我们办公中的主要方式&#xff0c;偶尔我们也可能会让文字竖着排版&#xff0c;让文字竖着排版我们都知道怎么操作&#xff0c;但是如何让英文和数字也纵向显示呢&#xff1f;接下来这篇文章就告诉你…

探索未来:Web3如何改变我们的生活方式

在数字化的时代&#xff0c;技术的不断发展和创新已经成为了我们生活的常态。而在这个不断变革的过程中&#xff0c;区块链技术作为一种颠覆性的技术&#xff0c;正逐渐成为人们关注的焦点。作为区块链技术的下一代&#xff0c;Web3正日益崭露头角&#xff0c;成为了未来的发展…

Eclipse项目间的引用

我们在开发的时候&#xff0c;有时候需要把一个大的项目打散&#xff0c;尤其是现在微服务的架构很流行&#xff0c;一个大的项目往往被拆成很多小的项目&#xff0c;而有的项目作为公共工程被独立出来&#xff0c;比如有个工程专门提供各种Util工具类&#xff0c;有的工程专门…

C#中使用list封装多个同类型对象以及组合拓展实体的应用

文章目录 一、list使用方法二、C#组合拓展实体 一、list使用方法 在C#中&#xff0c;使用List<T>集合是封装多个同类型对象的常用方式。List<T>是泛型集合&#xff0c;T是集合中元素的类型。下面是一个简单的例子&#xff0c;演示如何创建一个List<T>&#…