美文网首页
线程同步之使用信号量semaphore

线程同步之使用信号量semaphore

作者: Fsn_soul | 来源:发表于2017-02-25 17:46 被阅读177次

    线程同步之使用信号量semaphore

    基本概念

    信号量:信号量就是一个整数,并且具有一个初始计数值。支持两个操作:1.信号通知,2.等待。当一个信号量被通知时,信号量就会加1,当一个信号量收到等待通知时,信号量就减1.

    操作GCD信号量的函数

    在iOS中有三个函数可以操作GCD信号量:
    dispatch_semaphore_t dispatch_semaphore_create(long value); 创建一个信号量。
    long dispatch_semaphore_signal(dispatch_semaphore_t dsema); 发送一个信号.
    long dispatch_semaphore_wait(dispatch_semaphore_t dsema, dispatch_time_t timeout); 等待一个信号。

    函数说明:

    1. dispatch_semaphore_t semaphoreTask1 = dispatch_semaphore_create(0);
      创建一个信号量,并赋予初始值(最好传一个>=0的值).

    2. long signalRs = dispatch_semaphore_signal(semaphoreTask1);
      发出一个信号.将信号量加1.如果之前的信号量小于0,表明当前线程正处于等待(阻塞)状态,那么该函数在返回前将唤醒处于等待状态的线程.返回值:This function returns non-zero if a thread is woken. Otherwise, zero is returned.

    3. long waitRs = dispatch_semaphore_wait(semaphoreTask1, DISPATCH_TIME_FOREVER);
      将信号量-1.减去1后如果信号量<0,则该函数在返回前将一直等待信号的发出(即在没收到信号发出时将一直阻塞住当前线程.收到信号发出后,才不阻塞).否则不阻塞.返回值:Returns zero on success, or non-zero if the timeout occurred.

    使用场景

    假设有A,B两个异步的大任务(需要较多时间才能完成),需要等到这两个异步任务都完成之后,再用它们的结果进行下一步处理.

    OK,场景很简单,没毛病.此时,我们自然想到要使用GCD里面的组来解决,在dispatch_group_notify()里面获取到A,B任务的结果,进行下一步的操作.
    A,B两个任务分别放在dispatch_group_async()完成.
    代码如下:

    - (void)semaphoreSomething
    {
        dispatch_group_t group = dispatch_group_create();
        
        __block float a = 0;
        dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
            //实际要处理的任务,异步执行的
            [self task1WithCompletion:^(float rs) {
                a = rs;
            }];
        });
        
        __block float b = 0;
        dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
            [self task2WithCompletion:^(float rs) {
                b = rs;
                dispatch_semaphore_signal(semaphoreTask2);
            }];
        });
        
        dispatch_group_notify(group, dispatch_get_main_queue(), ^{
            float c = a - b;
            NSLog(@"c = a - b = %f - %f = %f", a, b, c);
        });
    }
    
    - (void)task1WithCompletion:(void (^)(float rs))completion
    {
        dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
            float rs = [self bigTaskWithCnt:60000000 identify:@"task1"];
            if (completion) {
                completion(rs);
            }
        });
    }
    
    - (void)task2WithCompletion:(void (^)(float rs))completion
    {
        dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
            float rs = [self bigTaskWithCnt:30000000 identify:@"task2"];
            if (completion) {
                completion(rs);
            }
        });
    }
    
    - (float)bigTaskWithCnt:(NSInteger)cnt identify:(NSString *)identify
    {
        NSLog(@"大任务%@开始,线程:%@",identify, [NSThread currentThread]);
        
        float f = 0.0;
        for (NSInteger i = 0; i < cnt; i++) {
            f = f + sin(sin(sin(time(NULL) + i)));
        }
        
        NSLog(@"大任务%@完成:f = %f\n",identify, f);
        
        return f;
    }
    
    

    当你颤抖着运行程序时,却发现,结果并不是你期望的.

    2017-02-25 17:19:01.900 信号量[14903:429880] 将要task2,线程:<NSThread: 0x608000268140>{number = 3, name = (null)}
    2017-02-25 17:19:01.900 信号量[14903:429883] 将要task1,线程:<NSThread: 0x600000264500>{number = 4, name = (null)}
    2017-02-25 17:19:01.900 信号量[14903:429880] 大任务task2开始,线程:<NSThread: 0x608000268140>{number = 3, name = (null)}
    2017-02-25 17:19:01.901 信号量[14903:429883] 大任务task1开始,线程:<NSThread: 0x600000264500>{number = 4, name = (null)}
    2017-02-25 17:19:01.901 信号量[14903:429599] 任务完成处理结果线程:<NSThread: 0x608000077b00>{number = 1, name = main}
    2017-02-25 17:19:01.901 信号量[14903:429599] c = a - b = 0.000000 - 0.000000 = 0.000000
    2017-02-25 17:19:11.016 信号量[14903:429880] 大任务task2完成:f = 1.699517
    2017-02-25 17:19:20.315 信号量[14903:429883] 大任务task1完成:f = -3.321420
    

    这是因为任务都是异步的,导致操作很快就结束了,这样程序就会进入dispatch_group_notify()里,执行最后的处理.而由于任务并没有真正完成,所以结果当然不是你所期望的.于是线程同步的问题就出现了!如何让A,B任务真正完成之后才进入dispatch_group_notify()里?

    我们知道semaphore可以通过发出信号和等待信号来让线程唤醒或阻塞.这正是我们所需要的,通过信号量我们可以将操作所在的线程先阻塞住,等到任务真正完成的时候再唤醒线程.

    修改上面的代码为:

    - (void)semaphoreSomething
    {
        dispatch_group_t group = dispatch_group_create();
        
        __block float a = 0;
        dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
            NSLog(@"将要task1,线程:%@", [NSThread currentThread]);
            //创建一个信号量
            dispatch_semaphore_t semaphoreTask1 = dispatch_semaphore_create(0);
            //实际要处理的任务
            [self task1WithCompletion:^(float rs) {
                a = rs; //任务结束.
                long signalRs = dispatch_semaphore_signal(semaphoreTask1);
                NSLog(@"发送信号函数返回结果--非0则表明线程已被唤醒:%ld", signalRs);
            }];
            //信号量初始化的时候是0,这里-1后=-1<0,于是阻塞住当前的子线程.
            long waitRs = dispatch_semaphore_wait(semaphoreTask1, DISPATCH_TIME_FOREVER);
            NSLog(@"等待信号函数返回结果--0表明成功,非0表明超时:%ld", waitRs);
        });
        
        __block float b = 0;
        dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
            NSLog(@"将要task2,线程:%@", [NSThread currentThread]);
            dispatch_semaphore_t semaphoreTask2 = dispatch_semaphore_create(0);
            [self task2WithCompletion:^(float rs) {
                b = rs;
                dispatch_semaphore_signal(semaphoreTask2);
            }];
            dispatch_semaphore_wait(semaphoreTask2, DISPATCH_TIME_FOREVER);
        });
        
        dispatch_group_notify(group, dispatch_get_main_queue(), ^{
            NSLog(@"任务完成处理结果线程:%@", [NSThread currentThread]);
            float c = a - b;
            NSLog(@"c = a - b = %f - %f = %f", a, b, c);
        });
    }
    

    再次执行,结果就正常了:

    2017-02-25 17:17:02.919 信号量[14870:428590] 将要task1,线程:<NSThread: 0x608000261340>{number = 4, name = (null)}
    2017-02-25 17:17:02.919 信号量[14870:428589] 将要task2,线程:<NSThread: 0x600000264e40>{number = 3, name = (null)}
    2017-02-25 17:17:02.919 信号量[14870:428592] 大任务task1开始,线程:<NSThread: 0x60000007dd80>{number = 5, name = (null)}
    2017-02-25 17:17:02.919 信号量[14870:428618] 大任务task2开始,线程:<NSThread: 0x60000007c200>{number = 6, name = (null)}
    2017-02-25 17:17:11.947 信号量[14870:428618] 大任务task2完成:f = 2.249237
    2017-02-25 17:17:21.144 信号量[14870:428592] 大任务task1完成:f = 2.456331
    2017-02-25 17:17:21.145 信号量[14870:428592] 发送信号函数返回结果--非0则表明线程已被唤醒:1
    2017-02-25 17:17:21.145 信号量[14870:428590] 等待信号函数返回结果--0表明成功,非0表明超时:0
    2017-02-25 17:17:21.145 信号量[14870:428499] 任务完成处理结果线程:<NSThread: 0x600000070d00>{number = 1, name = main}
    2017-02-25 17:17:21.145 信号量[14870:428499] c = a - b = 2.456331 - 2.249237 = 0.207094
    

    当然信号量的使用并不局限于此,信号量可以进行一些细粒度很高的排他操作.

    看到这里,我估计肯定有一些小伙伴会对里面的操作,任务等词语感到困惑.没办法很多理解障碍都是因为翻译导致的,本来两个不同的英文单词结果翻译过来都是同一个中文单词.比如"property"和"attribute",翻译过来都是"属性".如果你看的技术文章是中文翻译的,你绝对会一脸懵逼.

    相关文章

      网友评论

          本文标题:线程同步之使用信号量semaphore

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