美文网首页面试资料收集
dispatch_semaphore_t信号量

dispatch_semaphore_t信号量

作者: CharmecarWang | 来源:发表于2018-11-05 18:14 被阅读0次

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

    • 假设现在系统有两个空闲资源可以被利用,但同一时间却有三个线程要进行访问,这种情况下,该如何处理呢?
    • 我们要下载很多图片,并发异步进行,每个下载都会开辟一个新线程,可是我们又担心太多线程肯定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的调用次数,否则导致崩溃。

    相关文章

      网友评论

        本文标题:dispatch_semaphore_t信号量

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