美文网首页
iOS 多线程 GCD(二)

iOS 多线程 GCD(二)

作者: ivylee_mr | 来源:发表于2019-04-08 09:58 被阅读0次

    前言

    本文主要接扫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), ^{}方法,在执行该线程方法的时候,出现了线程阻塞。
    • 所以在出现以上使用情况的时候,超出了信号量的使用范围。

    相关文章

      网友评论

          本文标题:iOS 多线程 GCD(二)

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