Dispatch Semaphore

Dispatch Semaphore 是持有计数的信号量。

  1. dispatch_semaphore_create 创建一个信号量的初始值
    传入的参数为long,输出一个dispatch_semaphore_t类型且值为value的信号量。
    值得注意的是,这里的传入的参数value必须大于或等于0,否则dispatch_semaphore_create会返回NULL。
  1. dispatch_semaphore_signal => 发送一个信号 这个函数会使传入的信号量dsema的值加1。
    返回值为long类型,当返回值为0时表示当前并没有线程等待其处理的信号量,其处理的信号量的值加1即可。
    当返回值不为0时,表示其当前有(一个或多个)线程等待其处理的信号量,并且该函数唤醒了一个等待的线程(当线程有优先级时,唤醒优先级最高的线程;否则随机唤醒)。
  2. dispatch_semaphore_wait => 等待一个信号
    这个函数会使传入的信号量dsema的值减1;
    如果dsema信号量的值大于0,该函数所处线程就继续执行下面的语句,并且将信号量的值减1;
    如果desema的值为0,那么这个函数就阻塞当前线程等待timeout(注意timeout的类型为dispatch_time_t,
    不能直接传入整形或float型数),如果等待的期间desema的值被dispatch_semaphore_signal函数加1了,
    且该函数(即dispatch_semaphore_wait)所处线程获得了信号量,那么就继续向下执行并将信号量减1。
    如果等待期间没有获取到信号量或者信号量的值一直为0,那么等到timeout时,其所处线程自动执行其后语句。
  1. timeout
    在设置timeout时,比较有用的两个宏:
    DISPATCH_TIME_NOW     表示当前;
    DISPATCH_TIME_FOREVER   表示未来;
    一般可以直接设置timeout为这两个宏其中的一个,或者自己创建一个dispatch_time_t类型的变量。
    创建dispatch_time_t类型的变量有两种方法,dispatch_time和dispatch_walltime。
    利用创建dispatch_time创建dispatch_time_t类型变量的时候一般也会用到这两个变量。
    dispatch_time的声明如下:
    dispatch_time_t dispatch_time(dispatch_time_t when, int64_t delta);
    其参数when需传入一个dispatch_time_t类型的变量,和一个delta值。表示when加delta时间就是timeout的时间。
    例如:
    dispatch_time_t t = dispatch_time(DISPATCH_TIME_NOW, 110001000*1000);
    表示当前时间向后延时一秒为timeout的时间。
    备注:
    关于时间的一些宏
    #define NSEC_PER_SEC 1000000000ull
    #define USEC_PER_SEC 1000000ull
    #define NSEC_PER_USEC 1000ull
    NSEC:纳秒。
    USEC:微秒。
    SEC:秒
    PER:每
    1 NSEC_PER_SEC,每秒有多少纳秒。
    2 USEC_PER_SEC,每秒有多少毫秒。(注意是指在纳秒的基础上)
    3 NSEC_PER_USEC,每毫秒有多少纳秒。

例1:使用dispatch semaphore去实现等待Block操作结束。

单个block

   dispatch_semaphore_t sem = dispatch_semaphore_create(0);      
[self methodWithABlock:^(id result){
      //写block中做的事情
      //结束等待
      dispatch_semaphore_signal(sem);
}];
//等待信号
dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);


多个block
dispatch_semaphore_t sem = dispatch_semaphore_create(0);
[self methodWithABlock:^(id result){
      //写block中做的事情
      dispatch_semaphore_signal(sem);
}];
[self methodWithABlock:^(id result){
      //写block中做的事情
      dispatch_semaphore_signal(sem);
}];
dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);
dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);
block中的block
dispatch_semaphore_t sem = dispatch_semaphore_create(0);
[self methodWithABlock:^(id result){
      //写block中做的事情
      dispatch_semaphore_signal(sem);
      [self methodWithABlock:^(id result){
          //写block中做的事情
          dispatch_semaphore_signal(sem);
      }];
}];
dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);
dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);

iOS 等待block执行完成(dispacth semaphore)

实例2:dispatch_semaphore_t 造成 EXC_BAD_INSTRUCTION 崩溃

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
    dispatch_semaphore_t semaphore = dispatch_semaphore_create(1);
    NSMutableArray *array = [NSMutableArray array];
 
    for (int i = 0; i < 4; i++) {

   NSString *str = [NSString stringWithFormat:@"%d-signal", i];
         [array addObject:str];
        printf("end -- %d\n",i);
   }
    void (^block)(NSString *text) = ^(NSString *text) {
          NSLog(@"%@", text);
          dispatch_semaphore_signal(semaphore);
      };
      dispatch_async(dispatch_get_global_queue(QOS_CLASS_DEFAULT, 0), ^{
         [array enumerateObjectsUsingBlock:^(NSString * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
             dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
             if(idx > 1) {
                 *stop = YES;
             } else {
                 block(obj);
             }
             NSLog(@"wait------");
             
         }];
     });
}

运行结果

2020-05-09 21:34:01.275179+0800 OCTestFirst[10536:298054] 0-signal
2020-05-09 21:34:01.275371+0800 OCTestFirst[10536:298054] wait------
2020-05-09 21:34:01.275499+0800 OCTestFirst[10536:298054] 1-signal
2020-05-09 21:34:01.275664+0800 OCTestFirst[10536:298054] wait------
2020-05-09 21:34:01.275789+0800 OCTestFirst[10536:298054] wait------

在这里插入图片描述
提示:Thread 4: EXC_BAD_INSTRUCTION (code=EXC_I386_INVOP, subcode=0x0 错误,断点断在了_destroy_helper_block 这里的问题所在是在_dispatch_semaphore_dispose上。

代码改为

  NSMutableArray *array = [NSMutableArray array];
    __block dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
    for (int i = 0; i < 4; i++) {
        NSString *str = [NSString stringWithFormat:@"%d-signal", i];
        [array addObject:str];
    }
    void (^block)(NSString *text) = ^(NSString *text) {
        NSLog(@"%@", text);
        dispatch_semaphore_signal(semaphore);
    };
    dispatch_async(dispatch_get_global_queue(QOS_CLASS_DEFAULT, 0), ^{
        [array enumerateObjectsUsingBlock:^(NSString * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
            
            if(idx > 1) {
                *stop = YES;
                dispatch_semaphore_signal(semaphore);
                NSLog(@"%@", obj);
            } else {
                block(obj);
            }
            dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
            NSLog(@"wait------");
        }];
    });

运行结果

2019-07-26 11:41:13.759351+0800 CCNetwork[99872:22495958] 0-signal
2019-07-26 11:41:13.759495+0800 CCNetwork[99872:22495958] wait------
2019-07-26 11:41:13.759579+0800 CCNetwork[99872:22495958] 1-signal
2019-07-26 11:41:13.759657+0800 CCNetwork[99872:22495958] wait------
2019-07-26 11:41:13.759734+0800 CCNetwork[99872:22495958] 2-signal
2019-07-26 11:41:13.759810+0800 CCNetwork[99872:22495958] wait------

比对下前后两段代码和打印的日志可以看出:

  • 代码1,初始信号量为1,代码执行了2次dispatch_semaphore_signal,3次的dispatch_semaphore_wait。所以在代码运行结束前信号量为0.
  • 代码2,初始信号量为0,代码执行了3次的dispatch_semaphore_signal,3次的dispatch_semaphore_wait。所以在代码运行结束前信号量也是为0.

代码3

 NSMutableArray *array = [NSMutableArray array];
    dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
    for (int i = 0; i < 4; i++) {
        NSString *str = [NSString stringWithFormat:@"%d-signal", i];
        [array addObject:str];
    }
    void (^block)(NSString *text) = ^(NSString *text) {
        NSLog(@"%@", text);
        dispatch_semaphore_signal(semaphore);
    };
    dispatch_async(dispatch_get_global_queue(QOS_CLASS_DEFAULT, 0), ^{
        [array enumerateObjectsUsingBlock:^(NSString * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
            
            if(idx > 1) {
                *stop = YES;
                dispatch_semaphore_signal(semaphore);
                NSLog(@"%@", obj);
            } else {
                block(obj);
            }
            NSLog(@"wait------");
            dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
        }];
      dispatch_semaphore_signal(semaphore);
      NSLog(@"3-signal");
    });

运行结果日志:

2020-05-09 21:55:43.344631+0800 OCTestFirst[10830:309518] 0-signal
2020-05-09 21:55:43.344760+0800 OCTestFirst[10830:309602] wait------
2020-05-09 21:55:43.344869+0800 OCTestFirst[10830:309602] 1-signal
2020-05-09 21:55:43.344760+0800 OCTestFirst[10830:309602] wait------
2020-05-09 21:55:43.345057+0800 OCTestFirst[10830:309602] 2-signal
2020-05-09 21:55:43.344760+0800 OCTestFirst[10830:309602] wait------
2020-05-09 21:55:43.345350+0800 OCTestFirst[10830:309602] 3-signal

代码3能够正常运行,不会引起崩溃。由运行结果可以看出,代码3,初始信号量为0,代码执行了4次的dispatch_semaphore_signal,1次的dispatch_semaphore_wait。所以在代码运行结束前信号量是1.

代码3能够正常运行,不会引起崩溃。由运行结果可以看出,代码3,初始信号量为0,代码执行了4次的dispatch_semaphore_signal,3次的dispatch_semaphore_wait。所以在代码运行结束前信号量也是为1.

所以我们可以得到一个结论:
dispatch_semaphore_t调用_dispatch_semaphore_dispose 释放时,当前信号量值必须大于等于初始信号量值时,才能正常释放,否则将引起EXC_BAD_INSTRUCTION指令错误。

另外在dispatch_semaphore_t 处于wait状态时,释放这个dispatch_semaphore_t的引用(sema = nil),也会导致崩溃
dispatch_semaphore崩溃
遇到的 dispatch_group_t 类似错误崩溃时也可以借鉴dispatch_semaphore_t崩溃原因进行联想
group进行额外的leave操作从而造成crash(注:b1到b4因为leave的需要,会对group进行地址引用)
dispatch_semaphore_t 造成 EXC_BAD_INSTRUCTION 崩溃

实例3:dispatch_barrier_async

	dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
      dispatch_async(queue, ^{
            NSLog(@"任务A");
        });
        
        dispatch_async(queue, ^{
            NSLog(@"任务B");
        });
        
        dispatch_async(queue, ^{
            NSLog(@"任务C");
        });
        
        dispatch_barrier_async(queue, ^{
            NSLog(@"阻塞自定义并发队列");
        });
        
        dispatch_async(queue, ^{
            NSLog(@"任务D");
        });
        
        dispatch_async(queue, ^{
            NSLog(@"任务E");
        });

打印结果

2020-05-10 07:40:48.000366+0800 OCTestFirst[1800:33976] 阻塞自定义并发队列
2020-05-10 07:40:48.000398+0800 OCTestFirst[1800:33980] 任务A
2020-05-10 07:40:48.000397+0800 OCTestFirst[1800:33981] 任务B
2020-05-10 07:40:48.000413+0800 OCTestFirst[1800:33984] 任务C
2020-05-10 07:40:48.000565+0800 OCTestFirst[1800:33976] 任务D
2020-05-10 07:40:48.000606+0800 OCTestFirst[1800:33980] 任务E
注意:注意,这里用到的dispatch_barrier_async如果使用的队列是dispatch_global_queue,那么就等同意dispatch_async,起不到阻塞的作用。我们需要自己创建并发队列,然后再执行barrier函数,前面ABC三个任务随机,后面DE随机,但是DE的执行必须是等待ABC任务执行完的

如果将上例中queue改为自己创建的并发队列

   dispatch_queue_t queue = dispatch_queue_create(0, DISPATCH_QUEUE_CONCURRENT);

打印结果

2020-05-10 07:43:39.783157+0800 OCTestFirst[1857:36354] 任务C
2020-05-10 07:43:39.783185+0800 OCTestFirst[1857:36352] 任务B
2020-05-10 07:43:39.783202+0800 OCTestFirst[1857:36351] 任务A
2020-05-10 07:43:39.783452+0800 OCTestFirst[1857:36351] 阻塞自定义并发队列
2020-05-10 07:43:39.783587+0800 OCTestFirst[1857:36351] 任务D
2020-05-10 07:43:39.783591+0800 OCTestFirst[1857:36352] 任务E

可以看到,上面的都是异步并发操作,而里面的任务是同步的,这里的任务指的是Block里面的所有操作,但是如果Block里面的操作是网络请求,也是异步的,那上面的做法就会有问题了,看如下代码

🌰

    dispatch_group_t group = dispatch_group_create();
    dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
    dispatch_group_async(group, queue, ^{
        NSLog(@"同步任务A");
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0f * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
            NSLog(@"网络异步任务AA");
        });
    });
    
    dispatch_group_async(group, queue, ^{
        NSLog(@"同步任务B");
    });
    
    dispatch_group_async(group, queue, ^{
        NSLog(@"同步任务C");
    });
    
    dispatch_group_notify(group, queue, ^{
        NSLog(@"任务完成执行");
    });

// 2020-05-10 08:24:07.117888+0800 OCTestFirst[2279:50892] 同步任务A
// 2020-05-10 08:24:07.118070+0800 OCTestFirst[2279:50895] 同步任务B
// 2020-05-10 08:24:07.118106+0800 OCTestFirst[2279:50988] 同步任务C
// 2020-05-10 08:24:07.119289+0800 OCTestFirst[2279:50895] 任务完成执行
// 2020-05-10 08:24:09.118818+0800 OCTestFirst[2279:50803] 网络异步任务AA

用dispatch_after来模拟网络请求,可以看到,同步任务ABC->D这些操作还是正确的,但是里面有个模拟网络请求的任务,就不会按我们所想的顺序执行了。为什么呢?很简单,首先都是异步的,子线程操作,而且是并发队列,那么任务可以多个一起,如果任务是单纯的打印,即同步任务,那么就能完成我们的预期,如果Block任务里面还嵌套异步任务,因为并发队列里面的任务,只是负责打印和发送请求的操作,异步回调数据是不归队列管的,任务的执行完毕,只是Block代码块代码执行完,如果里面还包含异步任务,这里就需要通过信号量dispatch_semaphore来实现了。

上一题中的ABC三个任务改成异步任务(如AFN网络请求)、全部回调成功后进行数据整合。
如果只说队列/任务组肯定不行。因为网络请求本身是异步的、任务会立即完成、但数据还没有回来。
最好的就是在队列组的前提下。把异步的网络请求转化为同步、以捕获正确的完成时机。
具体操作需要使用信号量。

第一种 dispatch_group + semaphore
     dispatch_group_t group = dispatch_group_create();
       dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
       dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
       dispatch_group_async(group, queue, ^{
           NSLog(@"同步任务A");
           dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0f * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
               NSLog(@"网络异步任务一");
               dispatch_semaphore_signal(semaphore);
           });
           dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
       });
       
       dispatch_group_async(group, queue, ^{
           
           NSLog(@"同步任务B");
           dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.8f * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
               NSLog(@"网络异步任务二");
               dispatch_semaphore_signal(semaphore);
           });
           dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
       });
       
       dispatch_group_async(group, queue, ^{
           NSLog(@"同步任务C");
       });
       
       dispatch_group_async(group, queue, ^{
           
           NSLog(@"同步任务D");
           dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3.5f * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
               NSLog(@"网络异步任务四");
               dispatch_semaphore_signal(semaphore);
           });
           dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
       });
       
       dispatch_group_notify(group, queue, ^{
           NSLog(@"任务完成执行");
       });

打印结果
在这里插入图片描述
可以看到,Block整个就是一个任务,如果没有dispatch_semaphore_wait,带有网络请求的任务,因为网络请求本身是异步的、任务会立即完成、但数据还没有回来。,因为发送网络请求,就已经把Block的任务完成了,异步回来的操作已经不属于并发队列里面的管理的任务了。就和上面的错误例子一样,完成任务的执行,和请求回调没有任何顺序关系了。那么如果我们在Block里面加了dispatch_semaphore_wait,什么意思呢?如果信号量为0的时候,那么就会一直在这里等待。可以理解为一开始发送网络请求出去,这个时候执行到wait函数,信号量为0,等待,队列任务没有执行完,只有当请求回来的时候调用singnal的时候,信号+1,wait的函数随机获取到信号,放开任务,执行完毕一个,剩下的没有获取到的信号继续等待,那么就会按我们的,执行完一个网络请求,信号+1,释放一个wait,执行完一个Block任务,那么,当所有的网络请求执行完,所有的wait都被释放,任务都完成了,才会通知Group调用完成。

第二种 dispatch_group_enter 和 dispatch_group_leave
dispatch_group_t group = dispatch_group_create();
    dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
    dispatch_group_enter(group);
    dispatch_group_async(group, queue, ^{
        NSLog(@"同步任务A");
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0f * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
            NSLog(@"网络异步任务一");
            dispatch_group_leave(group);
        });
    });
    
    dispatch_group_enter(group);
    dispatch_group_async(group, queue, ^{
        
        NSLog(@"同步任务B");
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.8f * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
            NSLog(@"网络异步任务二");
            dispatch_group_leave(group);
        });
    });
    dispatch_group_enter(group);
    dispatch_group_async(group, queue, ^{
        NSLog(@"同步任务C");
        dispatch_group_leave(group);
    });
    
    dispatch_group_enter(group);
    dispatch_group_async(group, queue, ^{
        
        NSLog(@"同步任务D");
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3.5f * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
            NSLog(@"网络异步任务四");
            dispatch_group_leave(group);
        });
    });
    
    dispatch_group_notify(group, queue, ^{
        NSLog(@"任务完成执行");
    });

这种方法也是我之前项目中用的,也能实现上面的执行顺序。

ABCD异步顺序随机,无论任务是否有异步,都会等任务执行完(包括网络请求)再执行任务完成

总结
总结

异步并发执行同步任务,可以用dispatch_group,dispatch_barrier和NSOperation的依赖

异步串行执行同步任务,默认就是顺序执行

异步并发执行异步任务,可以用dispatch_group+semaphore 初始化信号为0,执行完一个异步通过singnal释放一个wait任务

或者用disaptch_group_enter 和 dispatch_group_leave

如果多个任务网络请求,NSOperation我感觉无法实现 多网络任务执行完之后再最终统一执行某一个人任务。这个时候只有用dispatch_group + semaphore来实现
异步并发下的任务是网络请求信号量的使用

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值