美文网首页iOS - 线程/底层/Runtime博客积累iOS异步多线程
IOS 多线程信号量的用法(解决异步线程中的线程等待问题)

IOS 多线程信号量的用法(解决异步线程中的线程等待问题)

作者: Abson在简书 | 来源:发表于2015-12-07 15:39 被阅读5845次

    hello,各位读者,我又回来了啦,感觉上一篇的文章各位的反映还算不错,感谢各位让我有坚持写作的动力。好了,前话就说这么多了,开始我们今天要说的主题了,最近博主在开发中碰到一个问题,开启两个主要异步线程,两个异步线程内部又得分别开启一个异步线程和其他耗时操作,最后还有第三个线程,这第三个线程必须等到前两个主要线程内部所有操作都完成以后再去执行,但是在执行以上这些操作的时候不能卡住界面,以下是我简单画的一个需求分析图

    需求分析图
    可能有些读者一眼看下去,会觉得这不是一个简单的线程组能搞掂的事情嘛?也许是的,那么我们来尝试一下,对需求进行线程组的功能制作

    1.第一个主要线程

    dispatch_group_t group = dispatch_group_create();
    dispatch_group_async(group, dispatch_queue_create("com.dispatch.test", DISPATCH_QUEUE_CONCURRENT), ^{
        NSURLRequest *request = [[NSURLRequest alloc] initWithURL:[NSURL URLWithString:@"https://www.baidu.com"]];
        NSURLSessionDownloadTask *task = [[NSURLSession sharedSession] downloadTaskWithRequest:request completionHandler:^(NSURL * _Nullable location, NSURLResponse * _Nullable response, NSError * _Nullable error) {
          // 请求完成,可以通知界面刷新界面等操作
          NSLog(@"第一步网络请求完成");
        }];
        [task resume];
          // 以下还要进行一些其他的耗时操作
          NSLog(@"耗时操作继续进行");
    });
    

    2.第二个主要线程(跟第一线程的操作是一样的。但是请求的地址不一样)

    dispatch_group_t group = dispatch_group_create();
    dispatch_group_async(group, dispatch_queue_create("com.dispatch.test", DISPATCH_QUEUE_CONCURRENT), ^{
        NSURLRequest *request = [[NSURLRequest alloc] initWithURL:[NSURL URLWithString:@"https://www.github.com"]];
        NSURLSessionDownloadTask *task = [[NSURLSession sharedSession] downloadTaskWithRequest:request completionHandler:^(NSURL * _Nullable location, NSURLResponse * _Nullable response, NSError * _Nullable error) {
          // 请求完成,可以通知界面刷新界面等操作
          NSLog(@"第二步网络请求完成");
        }];
        [task resume];
          // 以下还要进行一些其他的耗时操作
          NSLog(@"耗时操作继续进行");
    });
    

    3.第三个主要线程(等待前两个线程完全完成后再进行)

    dispatch_group_notify(group, dispatch_get_main_queue(), ^{
        NSLog(@"刷新界面等在主线程的操作");
    });
    

    那么,上面代码一眼看下去就很明了了,开启一个线程组,然后两个异步线程组 dispatch_group_t,最后一个 dispatch_group_notify 来执行依赖操作,但是当博主开开心心的跑去 command + R 的时候,奇怪的事情发生了,以下是是输出的结果:

    1 `NSLog(@"第一步网络请求完成");`
    2 `NSLog(@"刷新界面等在主线程的操作");`
    3 `NSLog(@"第二步网络请求完成");`
    

    读者不难发现,我们的数序不对了,第三步操作(刷新界面操作)明明是要第一步和第二步操作完成后才能进行的,我们已经设置了 dispatch_group_notify 了,但是为什么不是按照我们的思路去走呢?
    其实这个道理很简单,我们开启的网络请求,是一个异步线程,所谓的异步线程,就是告诉系统你不要管我是否完成了,你尽管执行其他操作,开一个线程让我到外面操作去执行就行了,所以我们傻傻的 dispatch_group_async 自然就不会管网络操作是否完成,是否有数据了,直接执行下面操作,告诉 dispatch_group_notify 它已经完成就行了。


    但这怎么办好呢?所以我们要引入了我们今天要用到的 多线程的信号量 dispatch_semaphore_t 了,那么 dispatch_semaphore_t又怎么理解么?

    ` dispatch_semaphore_t` :通俗的说我们可以理解成他是一个红绿灯的信号,当它的信号量为0时(红灯)等待,
      当信号量为1或大于1时(绿灯)走。
    

    以下就是它的创建跟使用:

    // 创建一个信号,value:信号量
    dispatch_semaphore_create(<#long value#>)
    // 使某个信号的信号量+1
    dispatch_semaphore_signal(<#dispatch_semaphore_t dsema#>)
    // 某个信号进行等待, timeout:等待时间,永远等待为 DISPATCH_TIME_FOREVER
    dispatch_semaphore_wait(<#dispatch_semaphore_t dsema#>, <#dispatch_time_t timeout#>)
    

    那么我们的代码可以改写为

    // 设置一个异步线程组
    dispatch_group_async(group, dispatch_queue_create("com.dispatch.test", DISPATCH_QUEUE_CONCURRENT), ^{
        // 设置一个网络请求
        NSURLRequest *request = [[NSURLRequest alloc] initWithURL:[NSURL URLWithString:@"https://www.github.com"]];
        // 创建一个信号量为0的信号(红灯)
        dispatch_semaphore_t sema = dispatch_semaphore_create(0);
        NSURLSessionDownloadTask *task = [[NSURLSession sharedSession] downloadTaskWithRequest:request completionHandler:^(NSURL * _Nullable location, NSURLResponse * _Nullable response, NSError * _Nullable error) {
            NSLog(@"第一步操作");
            // 使信号的信号量+1,这里的信号量本来为0,+1信号量为1(绿灯)
            dispatch_semaphore_signal(sema);
        }];
        [task resume];
         // 以下还要进行一些其他的耗时操作
          NSLog(@"耗时操作继续进行");
        // 开启信号等待,设置等待时间为永久,直到信号的信号量大于等于1(绿灯)
        dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER);
    });
    

    根据上面的写法,当线程执行到 dispatch_semaphore_wait 的时候如果网络请求还没有完成,那么信号就会继续等待,这个异步线程组就不会执行完毕,这样就能达到我们的需求了。

      当然  `dispatch_semaphore_signal` 的用途可不止这么一个喔,有兴趣的读者可以去多看看喔。
    

    @end

     最后感谢各位对我的支持,虽然不定期更新技术博客,但是我希望更新的博客都是精品,我会尽量写得更通俗
     易懂的,给 IOS 开发的各位朋友,共勉一下啊。
     令缺勿滥。
    

    心如止水,奋力前行

    相关文章

      网友评论

      • 注册麻烦:LZ,你下面用信号量的方法可以吗?我试了不行。你写的信号量的例子也只进行了一次异步操作,另一个 NSURLRequest *request = [[NSURLRequest alloc] initWithURL:[NSURL URLWithString:@"https://www.baidu.com";]];没有
      • Amuxiaomu:意思是不是,只要创建了信号量,这个异步操作只会等到这个信号量大于or等于1的时候,才会才会完成这个异步操作
      • CoderHw:你的线程组创建了两次?
        CoderHw:@月下伊人佳酒淳 恩,确实,如果请求是异步的,线程组则无法知道异步请求是否真正完成。dispatch_barrier也有类似情况。
        Abson在简书:@握握手好朋友 一次
      • 进化中的程序猿:我也想到两种方法
        1.你把你的网络请求换成同步的,就ok了
        2.使用dispatch_group_enter(group),dispatch_group_leave(group)
        Amuxiaomu:@进化中的程序猿666
      • 三生石上绛珠草:还有两种方案:
        1.dispatch_barrier_async
        2. NSOperation 设置依赖
        woshishui1243:@cocoyimasa dispatch_barrier_async如何实现?我认为以上用dispatch_group_enter和dispatch_group_leave是可以做到的
      • csqingyang:楼主 你不觉得你的线程组被你创建了两次吗? 使用线程组解决 你将他们放在一个线程组里 是不是就能解决C依赖AB的问题了?
        Abson在简书:@csqingyang 不是两次,是一次,这个问题是,一个线程组,如何处理两个并发线程,然后合一的处理,这样做的目的是加快两个不同耗时操作,然后所得数据进行处理的问题。

      本文标题:IOS 多线程信号量的用法(解决异步线程中的线程等待问题)

      本文链接:https://www.haomeiwen.com/subject/dmrehttx.html