iOS之利用GCD信号量控制并发网络请求

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

在网络请求的开发中,经常会遇到两种情况,一种是我在一个界面需要同时请求多种数据,比如列表数据、广告数据等,全部请求到后再一起刷新界面。另一种是我的请求必须满足一定顺序,比如必须先请求个人信息,然后根据个人信息请求相关内容。这些要求对于普通的操作是可以做到并发控制和依赖操作的,但是对于网络请求这种需要时间的请求来说,效果往往与预期的不一样,这时候就需要用信号量来做一个控制。

GCD信号量

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

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

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

在使用的时候,往往会创建一个信号量,然后进行多个操作,每次操作都等待信号量大于0再操作,同时信号昂-1,操作完后将信号量+1,类似下面这个过程:

dispatch_semaphore_t sema = dispatch_semaphore_create(5);
for (100次循环操作) {
    dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER);
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        // 操作
        dispatch_semaphore_signal(sema);
    });
}

上面代码表示我要操作100次,但是控制允许同时并发的操作最多只有5次,当并发量达到5后,信号量就减小到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,这时候一直处于等待的代码也得以执行通过,任务才算作完成。

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

参考资料:
1、http://www.cocoachina.com/ios/20170428/19150.html
2、http://blog.csdn.net/fhbystudy/article/details/25918451


查看作者首页

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,847评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,208评论 1 292
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,587评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,942评论 0 205
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,332评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,587评论 1 218
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,853评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,568评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,273评论 1 242
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,542评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,033评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,373评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,031评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,073评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,830评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,628评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,537评论 2 269

推荐阅读更多精彩内容