热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

学会了iOS信号量dispatch_semaphore线程锁,你用起来得心应手!!!

学会了iOS信号量dispatch_semaphore线程锁,你用起来得心应手!!!-本篇文章从通过源码分析来研究其实现原理。GCD的源码在libdispatch库中实现的可以在A

本篇文章从通过源码分析来研究其实现原理。GCD的源码在libdispatch库中实现的可以在Apple Open Source下载。

1.创建信号量

通过dispatch_semaphore_create(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);

连注释一起贴上来了:

  • 根据初始值value创建一个计数信号量

  • 当两个线程需要协同完成任务时,传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;
}

代码比较简单,就是入参检查(非负),开辟内存空间,初始化结构体成员。

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);
  • 等待一个信号,会对信号量-1,如果信号量小于0,该函数不会返回,直到等到一个信号发生(signal

  • 第一个参数,不能为NULL

  • 第二个参数timeout:指定等待的超时时间

  • 返回值:成功返回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,如果剩余信号量>= 0,函数直接返回成功

  • 信号量小于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;
}
  • 函数内部创建一个局部的锁,这个锁会保存在我们通过dispatch_semaphore_create()创建的信号量的dsema_sema成员,即dsema->dsema_sema

  • timeout

    • 大多数情况下我们会使用DISPATCH_TIME_FOREVER,这个分支下,会直接wait,使线程进入休眠,等到信号后会唤醒线程

    • 自定义超时时间会走default分支,也会休眠线程,不同的是,它是计时休眠,如果超时线程也会被唤醒

    • DISPATCH_TIME_NOW:超时时间就是现在,会立即超时,不知道什么场景会用。这个分支会这么写是因为上面default分支,如果是超时唤醒了线程,会直接「贯穿」到该分支,超时是在这个分支返回的

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);
  • 对计数信号量+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

  • 如果如果信号量大于0,直接返回,

  • 如果信号量<= 0,说明之前有线程在休眠等待信号,调用_dispatch_semaphore_signal_slow()发出一个信号来唤醒一个线程

4.总结

  • 所谓等待的线程,是指调用dispatch_semaphore_wait()时所在的线程

  • 从其他线程发出信号dispatch_semaphore_signal,会唤醒一个等在中的线程

  • GCDdispatch_semaphore就是一个计数信号量,通过这个计数量来管理线程,使线程或休眠等待,或唤醒执行任务

GCD的信号量是对系统内核信号量的一层封装,要想更深入的了解,可以去研究一下Linux内核的信号量。

截止到本篇文章,一共分析了iOS中7中锁,最后借用网络上流行的一张图(对比了iOS中所有锁的性能):

图中有九种锁:

  • OSSpinLock已被苹果废弃,取而代之的是os_unfair_lock

  • pthread_mutexpthread_mutex(recursive)属于pthread范畴

  • 除以上三种底层锁外,图中剩余的锁全部都在本专栏文章中介绍了用法以及通过源码分析研究了实现原理

推荐阅读
  • PHP反射API的功能和用途详解
    本文详细介绍了PHP反射API的功能和用途,包括动态获取信息和调用对象方法的功能,以及自动加载插件、生成文档、扩充PHP语言等用途。通过反射API,可以获取类的元数据,创建类的实例,调用方法,传递参数,动态调用类的静态方法等。PHP反射API是一种内建的OOP技术扩展,通过使用Reflection、ReflectionClass和ReflectionMethod等类,可以帮助我们分析其他类、接口、方法、属性和扩展。 ... [详细]
  • 先看看ElementUI里关于el-table的template数据结构:<template><el-table:datatableData><e ... [详细]
  • 使用nodejs爬取b站番剧数据,计算最佳追番推荐
    本文介绍了如何使用nodejs爬取b站番剧数据,并通过计算得出最佳追番推荐。通过调用相关接口获取番剧数据和评分数据,以及使用相应的算法进行计算。该方法可以帮助用户找到适合自己的番剧进行观看。 ... [详细]
  • Webpack5内置处理图片资源的配置方法
    本文介绍了在Webpack5中处理图片资源的配置方法。在Webpack4中,我们需要使用file-loader和url-loader来处理图片资源,但是在Webpack5中,这两个Loader的功能已经被内置到Webpack中,我们只需要简单配置即可实现图片资源的处理。本文还介绍了一些常用的配置方法,如匹配不同类型的图片文件、设置输出路径等。通过本文的学习,读者可以快速掌握Webpack5处理图片资源的方法。 ... [详细]
  • 本文讨论了在手机移动端如何使用HTML5和JavaScript实现视频上传并压缩视频质量,或者降低手机摄像头拍摄质量的问题。作者指出HTML5和JavaScript无法直接压缩视频,只能通过将视频传送到服务器端由后端进行压缩。对于控制相机拍摄质量,只有使用JAVA编写Android客户端才能实现压缩。此外,作者还解释了在交作业时使用zip格式压缩包导致CSS文件和图片音乐丢失的原因,并提供了解决方法。最后,作者还介绍了一个用于处理图片的类,可以实现图片剪裁处理和生成缩略图的功能。 ... [详细]
  • JDK源码学习之HashTable(附带面试题)的学习笔记
    本文介绍了JDK源码学习之HashTable(附带面试题)的学习笔记,包括HashTable的定义、数据类型、与HashMap的关系和区别。文章提供了干货,并附带了其他相关主题的学习笔记。 ... [详细]
  • 本文介绍了如何使用动态尺寸巧妙地将R中的数组子集化。作者通过解释数组的三个维度以及第三个维度的长度可变性,提出了一种周期性子集化数组的方法,并举例说明了如何创建第二个数组。这个方法对于制作模拟模型非常有用。 ... [详细]
  • React项目中运用React技巧解决实际问题的总结
    本文总结了在React项目中如何运用React技巧解决一些实际问题,包括取消请求和页面卸载的关联,利用useEffect和AbortController等技术实现请求的取消。文章中的代码是简化后的例子,但思想是相通的。 ... [详细]
  • 本文介绍了在wepy中运用小顺序页面受权的计划,包含了用户点击作废后的从新受权计划。 ... [详细]
  • 本文介绍了如何使用elementui分页组件进行分页功能的改写,只需一行代码即可调用。通过封装分页组件,避免在每个页面都写跳转请求的重复代码。详细的代码示例和使用方法在正文中给出。 ... [详细]
  • VueCLI多页分目录打包的步骤记录
    本文介绍了使用VueCLI进行多页分目录打包的步骤,包括页面目录结构、安装依赖、获取Vue CLI需要的多页对象等内容。同时还提供了自定义不同模块页面标题的方法。 ... [详细]
  • node.jsrequire和ES6导入导出的区别原 ... [详细]
  • Vue3中setup函数的参数props和context配置详解
    本文详细介绍了Vue3中setup函数的参数props和context的配置方法,包括props的接收和配置声明,以及未通过props进行接收配置时的输出值。同时还介绍了父组件传递给子组件的值和模板的相关内容。阅读本文后,读者将对Vue3中setup函数的参数props和context的配置有更深入的理解。 ... [详细]
  • 本文讨论了将HashRouter改为Router后,页面全部变为空白页且没有报错的问题。作者提到了在实际部署中需要在服务端进行配置以避免刷新404的问题,并分享了route/index.js中hash模式的配置。文章还提到了在vueJs项目中遇到过类似的问题。 ... [详细]
  • 前段时间做一个项目,需求是对每个视频添加预览图,这个问题最终选择方案是:用canvas.toDataYRL();来做转换获取视频的一个截图,添加到页面中,达到自动添加预览图的目的。 ... [详细]
author-avatar
aska
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有