iOS 多线程 GCD(二)

前言

本文主要接扫GCD的信号量相关的内容。

代码的下载地址demo;

1、信号量简介

此处省略一万字......

2、信号量的三个主要方法(函数)说明

dispatch_semaphore_create(long value);
方法作用:创建信号总量,即初始信号量允许的最大值,例如
dispatch_semaphore_t sem = dispatch_semaphore_create(0);
参数:信号总量的初值,数据类型为long类型。
返回值:如果value小于0,创建的sem对象其实是NULL类型。

dispatch_semaphore_signal(dispatch_semaphore_t deem);
方法作用:发送信号量 ,例如
dispatch_semaphore_signal(sem);
参数: dispatch_semaphore_t对象,比如刚才创建的sem
返回值:返回值为long类型。
当返回值为0时,表示当前并没有线程等待其处理的信号量,其处理的信号总量增加1
当返回值不为0时,表示其当前有一个或者多个线程等待其处理的信号量,并且该函数唤醒了一个等待的线程(当线程有优先级的时候,唤醒优先级最高的线程,否则随机唤醒)

dispatch_semaphore_wait(dispatch_semaphore_t dsema, dispatch_time_t timeout);
方法作用: 等待信号量,例如
dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);
参数: dispatch_semaphore_t对象和 超时时间类型DISPATCH_TIME_FOREVERDISPATCH_TIME_NOW。目前一般都是选择DISPATCH_TIME_FOREVER
DISPATCH_TIME_FOREVERDISPATCH_TIME_NOW具体区别如下:

  • DISPATCH_TIME_FOREVER 超时时间为永远,表示会一直等待信号量为正数,才会继续运行
  • DISPATCH_TIME_NOW 超时时间为0,表示忽略信号量,直接运行。

3、如何使用信号量?

下面我们看下如下代码,分别设置信号量semValue01234时的不同信号量初始值的打印结果

- (void)semaphoreTestMethodWithSemValue:(long)semValue{
    NSLog(@"\n\n\n\n");
    NSLog(@"semaphoreTestMethod 总任务开启");
    dispatch_semaphore_t semaphore = dispatch_semaphore_create(semValue);
    dispatch_queue_t quene = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    
    //任务1
    dispatch_async(quene, ^{
        NSLog(@"任务一开始");
        NSInteger currentSem = dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
        NSLog(@"任务一 currentSem = %ld:当前线程:%@",currentSem ,[NSThread currentThread]);
        
        [self netWorkingComletionHandler:^(NSString *status) {
            NSLog(@"任务一 任务Block回掉完成 %@",status);
        }];
        
        NSLog(@"完成任务一 currentSem = %ld:当前线程:%@",currentSem ,[NSThread currentThread]);
        dispatch_semaphore_signal(semaphore);
    });
    //任务2
    dispatch_async(quene, ^{
        NSLog(@"任务二开始");
        NSInteger currentSem = dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
        NSLog(@"任务二 currentSem = %ld:当前线程:%@",currentSem ,[NSThread currentThread]);
        [self netWorkingComletionHandler:^(NSString *status) {
            NSLog(@"任务二 任务Block回掉完成 %@",status);
        }];
        NSLog(@"完成任务二 currentSem = %ld:当前线程:%@",currentSem ,[NSThread currentThread]);
        dispatch_semaphore_signal(semaphore);
    });
    //任务3
    dispatch_async(quene, ^{
        NSLog(@"任务三开始");
        NSInteger currentSem = dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
        NSLog(@"任务三 currentSem = %ld:当前线程:%@",currentSem ,[NSThread currentThread]);
        [self netWorkingComletionHandler:^(NSString *status) {
            NSLog(@"任务三 任务Block回掉完成 %@",status);
        }];
        NSLog(@"完成任务三 currentSem = %ld:当前线程:%@",currentSem ,[NSThread currentThread]);
        dispatch_semaphore_signal(semaphore);
    });
    
    NSLog(@"semaphoreTestMethod 总任务关闭");
}

semValue = 0 打印结果

2019-04-03 17:15:32.008373+0800 GCDDemo[4793:333586] 
2019-04-03 17:15:32.008519+0800 GCDDemo[4793:333586] semaphoreTestMethod 总任务开启
2019-04-03 17:15:32.008647+0800 GCDDemo[4793:333586] semaphoreTestMethod 总任务关闭
2019-04-03 17:15:32.008695+0800 GCDDemo[4793:333639] 任务一开始
2019-04-03 17:15:32.008707+0800 GCDDemo[4793:333637] 任务二开始
2019-04-03 17:15:32.008750+0800 GCDDemo[4793:333811] 任务三开始
  • 这里设置的初始信号量为0时,我们的任务全部都被线程堵死,不在执行每个任务,因为没有任何资源执行任务。

semValue = 1 打印结果

2019-04-03 17:22:05.810571+0800 GCDDemo[4793:333586] semaphoreTestMethod 总任务开启
2019-04-03 17:22:05.810803+0800 GCDDemo[4793:333586] semaphoreTestMethod 总任务关闭
2019-04-03 17:22:05.810834+0800 GCDDemo[4793:333812] 任务一开始
2019-04-03 17:22:05.810948+0800 GCDDemo[4793:340419] 任务二开始
2019-04-03 17:22:05.810978+0800 GCDDemo[4793:340420] 任务三开始
2019-04-03 17:22:05.811148+0800 GCDDemo[4793:333812] 任务一 currentSem = 0:当前线程:<NSThread: 0x600002e8c080>{number = 3, name = (null)}
2019-04-03 17:22:05.811356+0800 GCDDemo[4793:333812] 完成任务一 currentSem = 0:当前线程:<NSThread: 0x600002e8c080>{number = 3, name = (null)}
2019-04-03 17:22:05.811557+0800 GCDDemo[4793:340419] 任务二 currentSem = 0:当前线程:<NSThread: 0x600002e8c480>{number = 5, name = (null)}
2019-04-03 17:22:05.811721+0800 GCDDemo[4793:340419] 完成任务二 currentSem = 0:当前线程:<NSThread: 0x600002e8c480>{number = 5, name = (null)}
2019-04-03 17:22:05.812068+0800 GCDDemo[4793:340420] 任务三 currentSem = 0:当前线程:<NSThread: 0x600002ee43c0>{number = 6, name = (null)}
2019-04-03 17:22:05.812324+0800 GCDDemo[4793:340420] 完成任务三 currentSem = 0:当前线程:<NSThread: 0x600002ee43c0>{number = 6, name = (null)}
2019-04-03 17:22:07.815793+0800 GCDDemo[4793:333586] 任务一 任务Block回掉完成 当前线程是:<NSThread: 0x600002e8c500>{number = 4, name = (null)} 随机等待:2秒
2019-04-03 17:22:07.816064+0800 GCDDemo[4793:333586] 任务三 任务Block回掉完成 当前线程是:<NSThread: 0x600002e8c480>{number = 5, name = (null)} 随机等待:2秒
2019-04-03 17:22:10.815416+0800 GCDDemo[4793:333586] 任务二 任务Block回掉完成 当前线程是:<NSThread: 0x600002e8c080>{number = 3, name = (null)} 随机等待:5秒
  • 这里设置的初始信号量为1时,任务会执行下去,但是每次只执行1条任务
  • 只有是在 dispatch_semaphore_wait()函数执行后返回值0的情况下,才会执行后面的回掉任务。
  • 无法控制block回掉中的耗时任务

semValue = 2 打印结果

2019-04-03 17:27:24.062999+0800 GCDDemo[4793:333586] semaphoreTestMethod 总任务开启
2019-04-03 17:27:24.063133+0800 GCDDemo[4793:333586] semaphoreTestMethod 总任务关闭
2019-04-03 17:27:24.063176+0800 GCDDemo[4793:333812] 任务一开始
2019-04-03 17:27:24.063244+0800 GCDDemo[4793:345396] 任务二开始
2019-04-03 17:27:24.063274+0800 GCDDemo[4793:345397] 任务三开始
2019-04-03 17:27:24.063346+0800 GCDDemo[4793:333812] 任务一 currentSem = 0:当前线程:<NSThread: 0x600002e8c080>{number = 3, name = (null)}
2019-04-03 17:27:24.063426+0800 GCDDemo[4793:345396] 任务二 currentSem = 0:当前线程:<NSThread: 0x600002e04580>{number = 7, name = (null)}
2019-04-03 17:27:24.063519+0800 GCDDemo[4793:333812] 完成任务一 currentSem = 0:当前线程:<NSThread: 0x600002e8c080>{number = 3, name = (null)}
2019-04-03 17:27:24.063587+0800 GCDDemo[4793:345396] 完成任务二 currentSem = 0:当前线程:<NSThread: 0x600002e04580>{number = 7, name = (null)}
2019-04-03 17:27:24.063746+0800 GCDDemo[4793:345397] 任务三 currentSem = 0:当前线程:<NSThread: 0x600002e04500>{number = 10, name = (null)}
2019-04-03 17:27:24.064110+0800 GCDDemo[4793:345397] 完成任务三 currentSem = 0:当前线程:<NSThread: 0x600002e04500>{number = 10, name = (null)}
2019-04-03 17:27:25.065614+0800 GCDDemo[4793:333586] 任务三 任务Block回掉完成 当前线程是:<NSThread: 0x600002e04580>{number = 7, name = (null)} 随机等待:1秒
2019-04-03 17:27:28.067425+0800 GCDDemo[4793:333586] 任务一 任务Block回掉完成 当前线程是:<NSThread: 0x600002e8c000>{number = 8, name = (null)} 随机等待:4秒
2019-04-03 17:27:28.067670+0800 GCDDemo[4793:333586] 任务二 任务Block回掉完成 当前线程是:<NSThread: 0x600002e8a400>{number = 9, name = (null)} 随机等待:4秒
  • 这里设置的初始信号量为2时,任务会执行下去,但是每次只执行2条任务
  • 只有是在 dispatch_semaphore_wait()函数执行后返回值0的情况下,才会执行后面的回掉任务。
  • 无法控制block回掉中的耗时任务

semValue = 3semValue = 4semValue = 2基本类似


通过以上的打印结果,我们可以知道:

  • 比如我们预计使用n个资源来处理发起的所有的排队任务,这里我们就需要创建一个初始可以使用信号总量n的信号量。
    dispatch_semaphore_t sem = dispatch_semaphore_create(n);
  • 在并发任务发起之后,我们会通过函数
    dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
    判断当前可用的信号量值,且进入线程阻塞状态。
    当该函数的返回值=0时,那么该函数后面的任务一直会进入线程阻塞状态,一直处于等待状态
    当该函数的返回值>0时,线程阻塞解除,执行该函数后面的方法。
  • 当函数dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER)的返回值>0时,线程阻塞取消,执行后面任务的同时,当前可用信号量会-1
  • 在执行任务完成以后我们会告知系统,我们的任务执行完成了,此时会通过函数
    dispatch_semaphore_signal(semaphore);
    告知系统任务执行完毕,释放了一个信号量,当前可用信号量会+1,同时通知其它dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);线程阻塞状态的函数,判断当前返回的信号量值如此循环,直到任务全部完成。

3、信号量+并发Block回掉

如果我们将信号量结合到回掉中会是如何呢?

#pragma mark - 信号量 + 回掉
- (void)semaphoreBlockTestMethodWithSemValue:(long)semValue{
    NSLog(@"\n\n\n\n");
    NSLog(@"semaphoreTestMethod 总任务开启");
    dispatch_semaphore_t semaphore = dispatch_semaphore_create(semValue);
    dispatch_queue_t quene = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    
    //任务1
    dispatch_async(quene, ^{
        NSLog(@"任务一开始");
        NSInteger currentSem = dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
        NSLog(@"任务一 currentSem = %ld:当前线程:%@",currentSem ,[NSThread currentThread]);
        
        [self netWorkingComletionHandler:^(NSString *status) {
            NSLog(@"任务一 任务Block回掉完成 %@",status);
            dispatch_semaphore_signal(semaphore);
        }];
        
        NSLog(@"完成任务一 currentSem = %ld:当前线程:%@",currentSem ,[NSThread currentThread]);
    });
    //任务2
    dispatch_async(quene, ^{
        NSLog(@"任务二开始");
        NSInteger currentSem = dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
        NSLog(@"任务二 currentSem = %ld:当前线程:%@",currentSem ,[NSThread currentThread]);
        [self netWorkingComletionHandler:^(NSString *status) {
            NSLog(@"任务二 任务Block回掉完成 %@",status);
            dispatch_semaphore_signal(semaphore);
        }];
        NSLog(@"完成任务二 currentSem = %ld:当前线程:%@",currentSem ,[NSThread currentThread]);
    });
    //任务3
    dispatch_async(quene, ^{
        NSLog(@"任务三开始");
        NSInteger currentSem = dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
        NSLog(@"任务三 currentSem = %ld:当前线程:%@",currentSem ,[NSThread currentThread]);
        [self netWorkingComletionHandler:^(NSString *status) {
            NSLog(@"任务三 任务Block回掉完成 %@",status);
            dispatch_semaphore_signal(semaphore);
        }];
        NSLog(@"完成任务三 currentSem = %ld:当前线程:%@",currentSem ,[NSThread currentThread]);
    });
    
    NSLog(@"semaphoreTestMethod 总任务关闭");
}

比如我们设置信号量的初始值为2,如下是打印结果

2019-04-03 18:09:59.097874+0800 GCDDemo[5095:376935] semaphoreTestMethod 总任务开启
2019-04-03 18:09:59.098011+0800 GCDDemo[5095:376935] semaphoreTestMethod 总任务关闭
2019-04-03 18:09:59.098036+0800 GCDDemo[5095:377194] 任务一开始
2019-04-03 18:09:59.098109+0800 GCDDemo[5095:390359] 任务二开始
2019-04-03 18:09:59.098139+0800 GCDDemo[5095:390360] 任务三开始
2019-04-03 18:09:59.098184+0800 GCDDemo[5095:377194] 任务一 currentSem = 0:当前线程:<NSThread: 0x6000025bad00>{number = 6, name = (null)}
2019-04-03 18:09:59.098282+0800 GCDDemo[5095:390359] 任务二 currentSem = 0:当前线程:<NSThread: 0x600002560240>{number = 7, name = (null)}
2019-04-03 18:09:59.098359+0800 GCDDemo[5095:377194] 完成任务一 currentSem = 0:当前线程:<NSThread: 0x6000025bad00>{number = 6, name = (null)}
2019-04-03 18:09:59.098444+0800 GCDDemo[5095:390359] 完成任务二 currentSem = 0:当前线程:<NSThread: 0x600002560240>{number = 7, name = (null)}
2019-04-03 18:10:01.100229+0800 GCDDemo[5095:376935] 任务一 任务Block回掉完成 当前线程是:<NSThread: 0x600002560680>{number = 8, name = (null)} 随机等待:2秒
2019-04-03 18:10:01.100646+0800 GCDDemo[5095:390360] 任务三 currentSem = 0:当前线程:<NSThread: 0x600002598880>{number = 10, name = (null)}
2019-04-03 18:10:01.100859+0800 GCDDemo[5095:390360] 完成任务三 currentSem = 0:当前线程:<NSThread: 0x600002598880>{number = 10, name = (null)}
2019-04-03 18:10:02.101269+0800 GCDDemo[5095:376935] 任务二 任务Block回掉完成 当前线程是:<NSThread: 0x60000257ce40>{number = 9, name = (null)} 随机等待:3秒
2019-04-03 18:10:06.103983+0800 GCDDemo[5095:376935] 任务三 任务Block回掉完成 当前线程是:<NSThread: 0x600002560680>{number = 8, name = (null)} 随机等待:5秒

  • 我们总任务有三个,设置的出事信号资源为2,当进入到并发任务后,由于只有2个可用信号资源,所以只有任务一任务二执行了回掉任务,其中任务三的回掉任务进入线程阻塞的等待中。
  • 任务一的延时回掉总共耗时2秒完成,完成后执行任务一block回掉,并通过dispatch_semaphore_signal(semaphore)告知释放了当前占用的1个信号量。
  • 剩余的任务三通过dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);延时等待的过程判断当前信号量已经>0了,判断执行任务的回掉函数任务。
  • 最终任务结束时间为任务一+任务三的耗时时间只和 与任务二的耗时时间对比,所以最终的耗时时间是7秒
  • 通过以上可以知道,如果将dispatch_semaphore_signal(semaphore)函数放入模拟网络请求的回掉中执行,可以控制在执行block回掉之后,通知线程阻塞等待状态的信号量,是否当前有资源处理剩余的任务。

4、实现异步多线程并发任务的同步操作

#pragma mark - 实现异步多线程并发任务的同步操作
- (void)semaphoreAsyncGlobalTask{
    NSLog(@"\n\n\n\n");
    NSLog(@"semaphoreAsyncGlobalTask 总任务开启");
    dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
    dispatch_queue_t quene = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    NSLog(@"任务执行 -> 开始线程:%@",[NSThread currentThread]);      // 打印当前线程
    dispatch_async(quene, ^{
        [NSThread sleepForTimeInterval:2];              // 模拟耗时操作
        NSLog(@"任务执行 一  -> 结束 线程:%@",[NSThread currentThread]);      // 打印当前线程
        dispatch_semaphore_signal(semaphore);
    });
    NSLog(@"任务执行 二 -> 结束  线程:%@",[NSThread currentThread]);      // 打印当前线程
    dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
    NSLog(@"任务执行 三 -> 结束  线程:%@",[NSThread currentThread]);      // 打印当前线程
    NSLog(@"semaphoreAsyncGlobalTask 总任务关闭");
}

打印结果

2019-04-04 11:47:12.788469+0800 GCDDemo[1713:126301] 任务执行 -> 开始线程:<NSThread: 0x600002c01440>{number = 1, name = main}
2019-04-04 11:47:12.788753+0800 GCDDemo[1713:126301] 任务执行 二 -> 结束  线程:<NSThread: 0x600002c01440>{number = 1, name = main}
2019-04-04 11:47:14.790166+0800 GCDDemo[1713:126762] 任务执行 一  -> 结束 线程:<NSThread: 0x600002c906c0>{number = 3, name = (null)}
2019-04-04 11:47:14.790478+0800 GCDDemo[1713:126301] 任务执行 三 -> 结束  线程:<NSThread: 0x600002c01440>{number = 1, name = main}
2019-04-04 11:47:14.790608+0800 GCDDemo[1713:126301] semaphoreAsyncGlobalTask 总任务关闭
  • 任务三实现了在任务一之后的同步执行
  • 任务二则没有实现同步执行,而是异步执行了。
  • 所以在实行多线程同步的执行任务的方式 dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);

我们再来看下面一段代码

#pragma mark - 实现异步多线程并发带Block回掉任务的同步操作
- (void)semaphoreAsyncGlobalBlockTask{
    NSLog(@"\n\n\n\n");
    NSLog(@"semaphoreAsyncGlobalBlockTask 总任务开启");
    dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
    dispatch_queue_t quene = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    NSLog(@"任务执行 -> 开始线程:%@",[NSThread currentThread]);      // 打印当前线程
    dispatch_async(quene, ^{
        NSLog(@"Block任务执行 -> 开始线程:%@",[NSThread currentThread]);      // 打印当前线程
        [self netWorkingComletionHandler:^(NSString *status) {
            NSLog(@"Block任务执行 一  -> 结束 线程:%@",[NSThread currentThread]);      // 打印当前线程
            dispatch_semaphore_signal(semaphore);
        }];
    });
    NSLog(@"任务执行 二 -> 结束  线程:%@",[NSThread currentThread]);      // 打印当前线程
    dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
    NSLog(@"任务执行 三 -> 结束  线程:%@",[NSThread currentThread]);      // 打印当前线程
    NSLog(@"semaphoreAsyncGlobalBlockTask 总任务关闭");
}

再看看其打印结果

2019-04-08 09:05:26.758092+0800 GCDDemo[1372:29281] 
2019-04-08 09:05:26.758280+0800 GCDDemo[1372:29281] semaphoreAsyncGlobalBlockTask 总任务开启
2019-04-08 09:05:26.758490+0800 GCDDemo[1372:29281] 任务执行 -> 开始线程:<NSThread: 0x600002abe940>{number = 1, name = main}
2019-04-08 09:05:26.758779+0800 GCDDemo[1372:29281] 任务执行 二 -> 结束  线程:<NSThread: 0x600002abe940>{number = 1, name = main}
2019-04-08 09:05:26.758780+0800 GCDDemo[1372:29342] Block任务执行 -> 开始线程:<NSThread: 0x600002aca280>{number = 3, name = (null)}
  • 我们发现此时dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);一直处于等待状态。而模拟的网络请求方法没有执行block 回掉任务。
  • 方法netWorkingComletionHandler中再次使用了dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{}方法,在执行该线程方法的时候,出现了线程阻塞。
  • 所以在出现以上使用情况的时候,超出了信号量的使用范围。
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容

  • 唯读书与走路不能辜负。
    路上的舞者阅读 250评论 0 0
  • “喂,已经出宫了,你可以放开了。”这手抓得死紧,可是疼死离雪了。 祁暗停下脚步,回头看她“我没有名字的吗?” “什...
    垣歌阅读 208评论 0 4
  • 在一回首间,才忽然发现,原来,我一生的种种努力,不过只为了周遭的人对我满意而已。为了搏得他人的称许与微笑,我战战兢...
    Myunt丶阅读 187评论 3 3
  • 暴灭宫殿。 通往地下的甬道处,都拿一脸阴沉的站立着,他手上有一个土黄色的薄衣,这颜色和人族龙晶的颜色竟然是一样的。...
    虎七阅读 648评论 3 2