37、iOS底层分析 - 线程锁(七)信号量 dispatch_semaphore

大纲

  1. 信号量介绍
  2. 信号量使用
  3. 信号量源码分析

 

一、信号量 dispatch_semaphore

信号量分析。GCD 的源码 在libdispatch 库中实现的,可以在 Apple Open Source下载 


 使用:

 1、dispatch_semaphore_create(value)
       创建信号量,value一般情况下传0
2、dispatch_semaphore_wait()
       等待信号量,会对信号量减1(value - 1),当信号量 < 0 时,会阻塞当前线程,等待信号(signal),当信号量 >= 0时,会执行wait后面的代码
3、dispatch_semaphore_signal()
      信号量加1,当信号量 >= 0 会执行wait之后的代码。
 因此 dispatch_semaphore_wait()dispatch_semaphore_signal() 要成对使用。

    - (IBAction)doTestButtonTouched:(id)sender {
        dispatch_queue_t globalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
        // 创建信号量
        dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
        
        // 添加任务1
        dispatch_async(globalQueue, ^{
            sleep(1);
            NSLog(@"task 1");
            // 任务1执行完毕,信号量 +1
            dispatch_semaphore_signal(semaphore);
        });
        
        // 添加任务2
        dispatch_async(globalQueue, ^{
            NSLog(@"task 2");
            // 任务2执行完毕,信号量 +1
            dispatch_semaphore_signal(semaphore);
        });
        
        NSLog(@"wait tasks...");
        dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
        NSLog(@"one task done!");
        dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
        NSLog(@"The other task done!");
        
        // 两次signal后信号量 = 0,执行接下来的代码
        NSLog(@"All tasks done!");
    }
 2020-04-08 10:17:34.828547+0800 filedome[30625:561172] wait tasks...
 2020-04-08 10:17:34.828547+0800 filedome[30625:561172] task 2
 2020-04-08 10:17:34.828547+0800 filedome[30625:561172] one task done!
 2020-04-08 10:17:34.828547+0800 filedome[30625:561172] task 1
 2020-04-08 10:17:34.828547+0800 filedome[30625:561172] The other task done!
 2020-04-08 10:17:34.828547+0800 filedome[30625:561172] All tasks done!

从输出结果可以看出,连续调用两次dispatch_semaphore_wait(),并不是会信号量连续两次-1,而是第一次wait -1之后阻塞线程,然后等到任务2 结束后的signal。此时信号量成为0,继续往下执行,wait -1,再次阻塞线程,任务1执行完后signal再到往下执行信息。
 wait 对信号量计数-1.如果结果值小于0,这个函数会在返回前一直等待 signal
 
 

二、源码分析

 1、创建信号量
 通过 dispatch_semaphore_create(long value)创建一个信号量
   

    / *!
     * @function dispatch_semaphore_create
     *
     * @abstract
     * Creates new counting semaphore with an initial value.
     *
     * @discussion
     * Passing zero for the value is useful for when two threads need to reconcile
     * the completion of a particular event. Passing a value greater than zero is
     * useful for managing a finite pool of resources, where the pool size is equal
     * to the value.
     *
     * @param value
     * The starting value for the semaphore. Passing a value less than zero will
     * cause NULL to be returned.
     *
     * @result
     * The newly created semaphore, or NULL on failure.
     * /
    API_AVAILABLE(macos(10.6), ios(4.0))
    DISPATCH_EXPORT DISPATCH_MALLOC DISPATCH_RETURNS_RETAINED DISPATCH_WARN_RESULT
    DISPATCH_NOTHROW
    dispatch_semaphore_t dispatch_semaphore_create(long value);

 根据初始值,values 创建一个计数信号量。当两个线程需要协同完成任务是,一般传0。当用来管理有限的资源池是,一般传大于0 的值。
 value 信号量的初始值。如果是小于0 的话,直接创建失败,函数返回NULL。
dispatch_semaphore_create()的实现:

    dispatch_semaphore_t dispatch_semaphore_create(long value)
    {
        dispatch_semaphore_t dsema;
        
        // If the internal value is negative, then the absolute of the value is
        // equal to the number of waiting threads. Therefore it is bogus to
        // initialize the semaphore with a negative value.
        if (value < 0) {
            return DISPATCH_BAD_INPUT;
        }
        
        dsema = _dispatch_object_alloc(DISPATCH_VTABLE(semaphore),
                                       sizeof(struct dispatch_semaphore_s));
        dsema->do_next = DISPATCH_OBJECT_LISTLESS;
        dsema->do_targetq = _dispatch_get_default_queue(false);
        dsema->dsema_value = value;
        _dispatch_sema4_init(&dsema->dsema_sema, _DSEMA4_POLICY_FIFO);
        dsema->dsema_orig = value;
        return dsema;
    }

1、先判断value是否小于0
2、开辟内存空间,初始化结构体成员。
3、返回初始化后的结构体
 
 
 2、信号量等待 dispatch_semaphore_wait
 等待一个信号量,会对信号量进行减1 操作,如果信号量小于0,该函数不会返回,直到等到一个信号量发送 signal(信号)
 第一个参数,不能为NULL
 第二个参数timerout: 指定等待的超时时间
 返回值,成功返回0,如果超时返回 !0

    long dispatch_semaphore_wait(dispatch_semaphore_t dsema, dispatch_time_t timeout)
    {
        long value = os_atomic_dec2o(dsema, dsema_value, acquire);
        if (likely(value >= 0)) {
            return 0;
        }
        return _dispatch_semaphore_wait_slow(dsema, timeout);
    }

 1、尝试获取锁,在说去锁的时候会对限号量-1,如果剩余信号量>=0,函数直接返回成功。
 2、信号量<0,调用_dispatch_semaphore_wait_slow,该函数会等到信号后才会返回
 _dispatch_semaphore_wait_slow

    DISPATCH_NOINLINE
    static long _dispatch_semaphore_wait_slow(dispatch_semaphore_t dsema,
                                              dispatch_time_t timeout)
    {
        long orig;
        
        _dispatch_sema4_create(&dsema->dsema_sema, _DSEMA4_POLICY_FIFO);
        switch (timeout) {
            default:
                if (!_dispatch_sema4_timedwait(&dsema->dsema_sema, timeout)) {
                    break;
                }
                // Fall through and try to undo what the fast path did to
                // dsema->dsema_value
            case DISPATCH_TIME_NOW:
                orig = dsema->dsema_value;
                while (orig < 0) {
                    if (os_atomic_cmpxchgvw2o(dsema, dsema_value, orig, orig + 1,
                                              &orig, relaxed)) {
                        return _DSEMA4_TIMEOUT();
                    }
                }
                // Another thread called semaphore_signal().
                // Fall through and drain the wakeup.
            case DISPATCH_TIME_FOREVER:
                _dispatch_sema4_wait(&dsema->dsema_sema);
                break;
        }
        return 0;
    }

1、函数内部创建一个局部的锁,这个锁会保存在我们通过 dispatch_semaphore_create() 创建的信号量的dsema_sema 成员,即dsema->dsema_sema
2、timeout
    (1)、大多数情况下我们会使用 DISPATCH_TIME_FOREVER。会直接wait,使线程进入休眠,等到信号后会唤醒线程。
    (2)、自定义超时时间会走default ,也会休眠线程,不同的是他是计时休眠。如果超时线程也会被唤醒。
    (3)、DISPATCH_TIMER_NOW 超时时间是现在,会立即超时。
 

 3、发送信号 dispatch_semaphore_signal

 对计数信号量+1,如果之前的信号量小于0,该函数会唤醒「一个」等待中的线程
 参数不能为NULL
 返回值:如果该函数唤醒了一个线程,返回非0,否则返回0

    long dispatch_semaphore_signal(dispatch_semaphore_t dsema)
    {
        long value = os_atomic_inc2o(dsema, dsema_value, release);
        if (likely(value > 0)) {
            return 0;
        }
        if (unlikely(value == LONG_MIN)) {
            DISPATCH_CLIENT_CRASH(value,
                                  "Unbalanced call to dispatch_semaphore_signal()");
        }
        return _dispatch_semaphore_signal_slow(dsema);
    }

 1、释放锁,同时对信号量+1
 2、如果信号量>0,直接返回
 3、如果信号量<=0,说明执勤啊有线程在休眠等待信号,调用 _dispatch_semaphore_signal_slow() 发出一个信号来唤醒一个线程
 

 总结:

 1、等待的线程:是指调用dispatch_semaphore_wait()时所在的线程
 2、从其他线程发出信号dispatch_semaphore_signal,会唤醒一个等在中的线程
 3、GCD的dispatch_semaphore 就是一个计数信号量,通过这个计数量来管理线程,使线程或休眠等待,或唤醒执行任务
 

发布了104 篇原创文章 · 获赞 13 · 访问量 19万+

猜你喜欢

转载自blog.csdn.net/shengdaVolleyball/article/details/105388190
今日推荐