dispatch_semaphore_t信号量

开发当中我们经常会碰到这种情况:

  • 假设现在系统有两个空闲资源可以被利用,但同一时间却有三个线程要进行访问,这种情况下,该如何处理呢?
  • 我们要下载很多图片,并发异步进行,每个下载都会开辟一个新线程,可是我们又担心太多线程肯定cpu吃不消,
    那么我们这里也可以用信号量控制一下最大开辟线程数。

1.定义:就是一种可用来控制访问资源的线程数量的标识,设定了一个信号量,在线程访问之前,加上信号量的处理,则可告知系统按照我们指定的信号量数量来执行多个线程。

2.信号量主要有3个函数,分别是:

//根据一个初始值创建信号量
dispatch_semaphore_create(信号量值)
//如果信号量的值<=0,当前线程就会进入休眠等待(直到信号量的值>0);如果信号量的值>0,就减1,然后往下执行后面的代码。
dispatch_semaphore_wait(信号量,等待时间)
//提高信号量(让信号量的值加1)
dispatch_semaphore_signal(信号量)

注意,这两个函数通常成对使用。

dispatch_semaphore实现的原理和自旋锁有点不一样。如果信号量小于等于0,它会使线程进入睡眠状态,让出cpu时间。直到信号量大于0或者超时,则线程会被重新唤醒执行后续操作。

3.我们举例解决一下刚开始提出的问题。

- (void)dispatchSignal {
    //创建信号量,参数:信号量的初值,如果小于0则会返回NULL
    dispatch_semaphore_t semaphore = dispatch_semaphore_create(2);
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    dispatch_async(queue, ^{
        //等待降低信号量
        dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
        NSLog(@"run task 1");
        sleep(1);
        NSLog(@"complete task 1");
        //提高信号量
        dispatch_semaphore_signal(semaphore);
    });
    dispatch_async(queue, ^{
        //等待降低信号量
        dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
        NSLog(@"run task 2");
        sleep(1);
        NSLog(@"complete task 2");
        //提高信号量
        dispatch_semaphore_signal(semaphore);
    });
    dispatch_async(queue, ^{
        //等待降低信号量
        dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
        NSLog(@"run task 3");
        sleep(1);
        NSLog(@"complete task 3");
        //提高信号量
        dispatch_semaphore_signal(semaphore);
    });
}

输出结果为:
2018-08-19 22:47:31.118267+0800 TestDate[19223:293646] run task 2
2018-08-19 22:47:31.118267+0800 TestDate[19223:293642] run task 1
2018-08-19 22:47:32.118646+0800 TestDate[19223:293642] complete task 1
2018-08-19 22:47:32.118646+0800 TestDate[19223:293646] complete task 2
2018-08-19 22:47:32.118824+0800 TestDate[19223:293644] run task 3
2018-08-19 22:47:33.121652+0800 TestDate[19223:293644] complete task 3
由于信号量的初始值为2,代表最多开两个线程,所以等待任务1和任务2执行之后才会执行任务3。

当我们将信号量的初始值为1,则是按顺序执行了。

2018-08-19 22:44:42.208884+0800 TestDate[19133:290481] run task 1
2018-08-19 22:44:43.212488+0800 TestDate[19133:290481] complete task 1
2018-08-19 22:44:43.212740+0800 TestDate[19133:290484] run task 2
2018-08-19 22:44:44.213660+0800 TestDate[19133:290484] complete task 2
2018-08-19 22:44:44.213835+0800 TestDate[19133:290482] run task 3
2018-08-19 22:44:45.214455+0800 TestDate[19133:290482] complete task 3

当我们将信号量的初始值为3,则是完全异步执行了。

2018-08-19 22:47:04.676448+0800 TestDate[19198:292866] run task 2
2018-08-19 22:47:04.676448+0800 TestDate[19198:292869] run task 1
2018-08-19 22:47:04.676464+0800 TestDate[19198:292868] run task 3
2018-08-19 22:47:05.679441+0800 TestDate[19198:292869] complete task 1
2018-08-19 22:47:05.679441+0800 TestDate[19198:292866] complete task 2
2018-08-19 22:47:05.679464+0800 TestDate[19198:292868] complete task 3

使用信号量完成同步操作

场景一:

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view.
    
    dispatch_semaphore_t sem = dispatch_semaphore_create(0);
    self.sem = sem;
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    dispatch_async(queue, ^{
        [self semaphore_signal];
        long semaphoreWait = dispatch_semaphore_wait(sem, dispatch_time(DISPATCH_TIME_NOW, 15 * NSEC_PER_SEC));
        NSLog(@"semaphoreWait: %ld", semaphoreWait);
        if (semaphoreWait == 0) {
            // 降低信号量成功
            NSLog(@"降低信号量成功");
        } else {
            // 降低信号量失败,线程休眠直到15s后会走到这里
            NSLog(@"降低信号量失败,线程休眠直到15s后会走到这里");
        }
    });
}

- (void)semaphore_signal {
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        dispatch_semaphore_signal(self.sem);
    });
}

输出结果为:

2020-03-03 17:30:20.447806+0800 TestSemaphore[73321:2061719] semaphoreWait: 0
2020-03-03 17:30:20.447978+0800 TestSemaphore[73321:2061719] 降低信号量成功

因为信号量为0的时候,线程阻塞等待,直到2s后,执行了dispatch_semaphore_signal,使得信号量加1,此时信号量大于0,dispatch_semaphore_wait就可以降低信号量成功。

场景二:
如果把[self semaphore_signal]这行代码注释,输出结果为:

2020-03-03 17:42:05.420673+0800 TestSemaphore[73349:2076403] semaphoreWait: 49
2020-03-03 17:42:05.421105+0800 TestSemaphore[73349:2076403] 降低信号量失败,线程休眠直到15s后会走到这里

这是因为信号量为0,线程阻塞等待,直到15s后,继续执行函数,但是semaphoreWait是大于0的,代表降低信号量失败

场景三:

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view.
    
    dispatch_semaphore_t sem = dispatch_semaphore_create(0);
    self.sem = sem;
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    dispatch_async(queue, ^{
//        [self semaphore_signal];
        long semaphoreWait = dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);
        NSLog(@"semaphoreWait: %ld", semaphoreWait);
        if (semaphoreWait == 0) {
            // 降低信号量成功
            NSLog(@"降低信号量成功");
        } else {
            // 降低信号量失败,线程休眠直到15s后会走到这里
            NSLog(@"降低信号量失败,线程休眠直到15s后会走到这里");
        }
    });
}

dispatch_semaphore_wait第二个参数改为DISPATCH_TIME_FOREVER,代表线程一直处于等待状态,不会输出任何东西

dispatch_semaphore 闪退问题

  dispatch_semaphore_t semp = dispatch_semaphore_create(1);
    dispatch_block_t block = ^{
        dispatch_semaphore_signal(semp);
        NSLog(@"signal");
    };

    NSMutableArray *array = [NSMutableArray array];
    for (NSInteger i = 0; i < 4; i++) {
        NSLog(@"wait");
        dispatch_semaphore_wait(semp, DISPATCH_TIME_FOREVER);
        if (i > 2) {//当I大于2时,只执行 wait ,没执行signal
            break;
        }else{ //当I小于等于2时,signal与wait是配对的
            block();
        }
    }

控制台输出如下:

019-07-29 11:12:02.300190+0800 TEST[10987:950615] +[CATransaction synchronize] called within transaction
2019-07-29 11:12:02.439848+0800 TEST[10987:950615] wait
2019-07-29 11:12:02.439934+0800 TEST[10987:950615] signal
2019-07-29 11:12:02.439962+0800 TEST[10987:950615] wait
2019-07-29 11:12:02.439986+0800 TEST[10987:950615] signal
2019-07-29 11:12:02.440009+0800 TEST[10987:950615] wait
2019-07-29 11:12:02.440032+0800 TEST[10987:950615] signal
2019-07-29 11:12:02.440054+0800 TEST[10987:950615] wait

上面代码执行后,wait数大于signal次数,即信号量的当前值小于初始化,超过函数作用域后,会释放信号量,此时会崩溃产生

原因是信号量的销毁会调用_dispatch_semaphore_dispose函数,而此函数会执行信号当前值与初始化值的比较,如果小于初始化值,则直接抛出崩溃。
我们看下此函数的源码:

static void
_dispatch_semaphore_dispose(dispatch_semaphore_t dsema)
{
    //信号量的当前值小于初始化,会发生闪退。因为信号量已经被释放了
    if (dsema->dsema_value < dsema->dsema_orig) {
        DISPATCH_CLIENT_CRASH(
                "Semaphore/group object deallocated while in use");
    }

#if USE_MACH_SEM
    kern_return_t kr;
    //释放信号,这个信号是dispatch_semaphore使用的信号
    if (dsema->dsema_port) {
        kr = semaphore_destroy(mach_task_self(), dsema->dsema_port);
        DISPATCH_SEMAPHORE_VERIFY_KR(kr);
    }
    //释放信号,这个信号是dispatch_group使用的信号
    if (dsema->dsema_waiter_port) {
        kr = semaphore_destroy(mach_task_self(), dsema->dsema_waiter_port);
        DISPATCH_SEMAPHORE_VERIFY_KR(kr);
    }
#elif USE_POSIX_SEM
    int ret = sem_destroy(&dsema->dsema_sem);
    DISPATCH_SEMAPHORE_VERIFY_RET(ret);
#endif

    _dispatch_dispose(dsema);
}

因此。当信号量的当前值小于初始化,释放信号量时,会导致崩溃,简而言之就是,signal的调用次数一定要大于等于wait的调用次数,否则导致崩溃。

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

推荐阅读更多精彩内容