线程同步之使用信号量semaphore

线程同步之使用信号量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的值)。eg:传入2则表示同时最多两个线程可以访问临界区。

  2. long signalRs = dispatch_semaphore_signal(semaphoreTask1);
    发出一个信号,将信号量加1。如果有通过dispatch_semaphore_wait()函数等待dispatch semaphore的计数值增加的线程,那么该函数在返回前将唤醒最先处于等待状态的线程。
    返回值:This function returns non-zero if a thread is woken. Otherwise, zero is returned。如果有线程被唤醒则返回一个非0值,否则返回0。

  3. long waitRs = dispatch_semaphore_wait(semaphoreTask1, DISPATCH_TIME_FOREVER);
    将信号量-1。减去1后如果得到的值<0,则该函数在返回前将一直等待信号的发出,线程会被阻塞。减去1后如果得到的值>0,则正常返回0,不阻塞线程。
    timeout:指定等待的时间。如果线程要被阻塞该值表明将阻塞该线程多久。传DISPATCH_TIME_FOREVER意味着该线程永久等待一个信号的发出。 传一个其他值如:dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)),表明将阻塞该线程2s,2s后函数将返回,线程继续执行(不一定就是执行临界区的代码,可以判断如果返回的是非0表明是等待超时了可以做其他处理)。
    返回值:Returns zero on success, or non-zero if the timeout occurred。

注意:dispatch_semaphore_signal() 与 dispatch_semaphore_wait() 必须配对使用,否则将导致EXC_BAD_INSTRUCTION崩溃。

使用场景

假设有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_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",翻译过来都是"属性".如果你看的技术文章是中文翻译的,你绝对会一脸懵逼.

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

推荐阅读更多精彩内容