面试资料收集

dispatch_semaphore_t信号量

2018-11-05  本文已影响0人  CharmecarWang

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

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的调用次数,否则导致崩溃。

上一篇下一篇

猜你喜欢

热点阅读