iOS 中的锁(4)

2020-11-12  本文已影响0人  just东东

iOS 中的锁(4)

不想篇幅太长,再开一篇继续探究iOS中的锁。

注:本文主要通过Objective-C语言进行体现,其实跟Swift也差不多。

本文介绍一种特殊的锁dispatch_semaphore信号量,然后对锁这个篇章的分析做个总结

1. dispatch_semaphore

关于dispatch_semaphore的应用在我的这篇文章中已经做了一些介绍。dispatch_semaphore属于GCD模块,源码实现自libdispatch库中,我们可以在Apple Open Source中下载各个版本的libdispatch源码。本文使用的是libdispatch-1173.40.5

信号量作为锁是一种特例,当信号量的value传0和1时可以具有锁的特性。
信号量主要有下列几个函数:

1.1 创建信号量 dispatch_semaphore_create

我们看看创建信号函数的定义:

/*!
 * @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(intptr_t value);
```*当两个线程需要协调时,传递0作为值非常有用
*完成某一特定事件。传递一个大于零的值是
*用于管理有限的资源池,其中池大小相等
*的值。

根据注释我们可以知道:

* 信号量是根据初始值`value`创建一个新的计数信号量
* 当两个线程需要协调完成一个特定的事件时,传0作为值非常有用
* 传递一个大于0的值值对于管理有限的资源池非常有用,其中池大小等于该值。
* 如果创建时传的值小于0则创建失败,对于创建失败的信号量会返回`NULL`

**dispatch_semaphore_create实现代码:**

```C
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.2 等待信号量 dispatch_semaphore_wait

等待信号量函数定义:

/*!
 * @function dispatch_semaphore_wait
 *
 * @abstract
 * Wait (decrement) for a semaphore.
 *
 * @discussion
 * Decrement the counting semaphore. If the resulting value is less than zero,
 * this function waits for a signal to occur before returning.
 *
 * @param dsema
 * The semaphore. The result of passing NULL in this parameter is undefined.
 *
 * @param timeout
 * When to timeout (see dispatch_time). As a convenience, there are the
 * DISPATCH_TIME_NOW and DISPATCH_TIME_FOREVER constants.
 *
 * @result
 * Returns zero on success, or non-zero if the timeout occurred.
 */
API_AVAILABLE(macos(10.6), ios(4.0))
DISPATCH_EXPORT DISPATCH_NONNULL_ALL DISPATCH_NOTHROW
long
dispatch_semaphore_wait(dispatch_semaphore_t dsema, dispatch_time_t timeout);

根据注释我们可以知道:

dispatch_semaphore_wait函数实现:

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);
}

等待信号量代码分析:

_dispatch_semaphore_wait_slow函数实现

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.3 发送信号量 dispatch_semaphore_signal

发送信号量函数定义:

/*!
 * @function dispatch_semaphore_signal
 *
 * @abstract
 * Signal (increment) a semaphore.
 *
 * @discussion
 * Increment the counting semaphore. If the previous value was less than zero,
 * this function wakes a waiting thread before returning.
 *
 * @param dsema The counting semaphore.
 * The result of passing NULL in this parameter is undefined.
 *
 * @result
 * This function returns non-zero if a thread is woken. Otherwise, zero is
 * returned.
 */
API_AVAILABLE(macos(10.6), ios(4.0))
DISPATCH_EXPORT DISPATCH_NONNULL_ALL DISPATCH_NOTHROW
long
dispatch_semaphore_signal(dispatch_semaphore_t dsema);

根据注释我们可以知道:

dispatch_semaphore_signal函数实现:

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);
}

_dispatch_semaphore_signal_slow源码:

long
_dispatch_semaphore_signal_slow(dispatch_semaphore_t dsema)
{
    _dispatch_sema4_create(&dsema->dsema_sema, _DSEMA4_POLICY_FIFO);
    _dispatch_sema4_signal(&dsema->dsema_sema, 1);
    return 1;
}

1.3 小结

  1. 信号量是GCDdispatch_semaphore就是一个计数信号量,通过信号量的数量来管理线程,当计数大于0时线程会去执行任务,当计数小于0就会使线程休眠等待,当等待到一个信号后,会唤醒一个等待的线程去执行任务。
  2. GCD信号量实际是调用了系统底层的信号量,对系统内核信号量的一层封装,感兴趣的可以去研究一下Linux内核的信号量。
  3. 信号量的锁主要是在信号量计数为1或0时,通过dispatch_semaphore_wait去等待线程执行时产生的锁的效果
  4. 这里的等待指的是在调用dispatch_semaphore_wait的线程中等待,等待一个信号去唤醒这个线程
  5. 发送信号,是指在在其他线程中调用dispatch_semaphore_signal函数增加一个信号量计数,如果计数<=0则会唤醒一个等待的线程

2. 总结

  1. 在iOS中锁的这几篇文章中我们一个介绍了NSLock@synchronizedNSConditionNSConditionLockNSRecursiveLockatomicdispatch_semaphore七种锁。并且在@synchronized篇章中还介绍了os_unfair_lock(OSSpinLock)

  2. 在这七种锁中NSLockNSConditionNSConditionLock是对pthread_mutex的封装。

  3. NSRecursiveLock是对pthread_mutex(recursive)的封装

  4. @synchronized维护了一张哈希表,对同一对象加锁时采用lockCount在递归锁中可以避免死锁的问题

  5. dispatch_semaphore是信号量为0和1时造成线程等待的锁现象

  6. atomic是OC特有的修饰声明属性的关键字,只保证在setget方法内的线程安全,在使用过程中不是绝对的线程安全。

  7. 其实还有一种锁NSDistributedLock使用在Mac开发中,感兴趣的可以到Mac开发中探索一下

锁性能对比.jpg

最后上一张大神的关于锁的性能图。这里OSSpinLock是一把自旋锁,在iOS10以后已经被苹果废弃了,用于替代它的是os_unfair_lock,原因是OSSpinLock虽然耗时断,但是占用资源多,这是一个已空间换时间的锁。

以上这些锁没有什么好坏之分,只是使用的环境各有不同而已,在开发中我们可以根据业务需求选择合适的锁进行使用。

上一篇下一篇

猜你喜欢

热点阅读