iOS底层 - 不见不散 的 信号量

这是我参与8月更文挑战的第15天,活动详情查看:8月更文挑战

写在前面: iOS底层原理探究是本人在平时的开发和学习中不断积累的一段进阶之
路的。 记录我的不断探索之旅,希望能有帮助到各位读者朋友。

目录如下:

  1. iOS 底层原理探索 之 alloc
  2. iOS 底层原理探索 之 结构体内存对齐
  3. iOS 底层原理探索 之 对象的本质 & isa的底层实现
  4. iOS 底层原理探索 之 isa - 类的底层原理结构(上)
  5. iOS 底层原理探索 之 isa - 类的底层原理结构(中)
  6. iOS 底层原理探索 之 isa - 类的底层原理结构(下)
  7. iOS 底层原理探索 之 Runtime运行时&方法的本质
  8. iOS 底层原理探索 之 objc_msgSend
  9. iOS 底层原理探索 之 Runtime运行时慢速查找流程
  10. iOS 底层原理探索 之 动态方法决议
  11. iOS 底层原理探索 之 消息转发流程
  12. iOS 底层原理探索 之 应用程序加载原理dyld (上)
  13. iOS 底层原理探索 之 应用程序加载原理dyld (下)
  14. iOS 底层原理探索 之 类的加载
  15. iOS 底层原理探索 之 分类的加载
  16. iOS 底层原理探索 之 关联对象
  17. iOS底层原理探索 之 魔法师KVC
  18. iOS底层原理探索 之 KVO原理|8月更文挑战
  19. iOS底层原理探索 之 重写KVO|8月更文挑战
  20. iOS底层原理探索 之 多线程原理|8月更文挑战
  21. iOS底层原理探索 之 GCD函数和队列
  22. iOS底层原理探索 之 GCD原理(上)
  23. iOS底层 - 关于死锁,你了解多少?
  24. iOS底层 - 单例 销毁 可否 ?
  25. iOS底层 - Dispatch Source
  26. iOS底层 - 一个栅栏函 拦住了 数

以上内容的总结专栏


细枝末节整理


前言

在之前的 iOS底层原理探索 之 GCD原理(上) 篇章中,我们重点对 GCD的函数和队列、 GCD的底层数据结构、同步函数底层调用和异步函数底层调用进行了剩入的分析。 而 在 后续的 关于死锁,你了解多少?销毁 一个 单例Dispatch Source 三篇分别对 GCD 使用中导致死锁的问题 、 单例底层实现逻辑 和 Dispatch Source 的应用内容。 今天,我们再来研究一下 GCD 部分的 栅栏函数底层实现,信号量和调度组的应用。 也算是 GCD 篇章的一个结尾。好的,下面就开始今天的内容。

信号量

在《程序员的自我修养》一书中的第26页 -- 二元信号量是比较相似的。

二元信号量 (Binary Semaphore)是最简单的一种锁,它只有两种状态:占用与非占 用。它适合只能被唯--个线程独占访问的资源。当二元信号量处于非占用状态时,第一个 试图获取该二元信号量的线程会获得该锁,并将二元信号量置为占用状态,此后其他的所有 试图获取该二元信号量的线程将会等待,直到该锁被释放。 对于允许多个线程并发访问的资源,多元信号量简称信号量(Semaphore),它是-一个 很好的选择。 一个初始值为 N 的信号量允许 N 个线程并发访问。线程访问资源的时候首先 荻取信号量,进行如下操作:

  • 将信号量的值减1。
  • 如果信号量的值小于0,则进入等待状态,否则继续执行。
  • 访间完资源之后,线程释放信号量,进行如下操作:
  • 将信号量的值加 1。
  • 如果信号量的值小于 1,唤醒一个等待中的线程。

我们要研究的信号量,就不只是 1 和 0 两种状态哦。

信号量的使用

  • dispatch_semaphore_create : 创建一个信号量,参数用来控制队列最大并发数

  • dispatch_semaphore_wait: 信号量等待,等待信号被释放之后,才会执行任务

  • dispatch_semaphore_signal : 信号量释放

例子

例1

例子1

控制最大并发数

    //自定义并发队列
    dispatch_queue_t queue = dispatch_queue_create("superman", DISPATCH_QUEUE_CONCURRENT);
    
    //创建信号量
    dispatch_semaphore_t sem = dispatch_semaphore_create(5);
    
    //添加任务
    for (int i = 0; i < 10; i++) {       
        dispatch_async(queue, ^{
        
           dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);
            NSLog(@"执行任务 -- %d", i);
            sleep(2);
            NSLog(@"任务%d完成", i);
            dispatch_semaphore_signal(sem);
        });
    }

例2

例子2

扫描二维码关注公众号,回复: 14477089 查看本文章

就算任务一先添加,任务二耗时更加严重,但是,仍然先执行任务二

    //自定义并发队列
    dispatch_queue_t queue = dispatch_queue_create("superman", DISPATCH_QUEUE_CONCURRENT);
    
    //创建信号量
    dispatch_semaphore_t sem = dispatch_semaphore_create(0);
    
    //任务1
     dispatch_async(queue, ^{
        dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER); // 等待
        NSLog(@"执行任务 -- 1");
        sleep(2);
        NSLog(@"任务1完成");

    });
    
    //任务2
    dispatch_async(queue, ^{

        NSLog(@"执行任务 -- 2");
        sleep(10);
        NSLog(@"任务2完成");
        dispatch_semaphore_signal(sem); // 发信号
    });

那么,底层中,信号量 是如何实现 最大并发数的控制的呢? 而且,dispatch_semaphore_signaldispatch_semaphore_wait 的配合使用可以实现同步,控制流程的效果。 我们去到底层源码查看下,dispatch_semaphore_signal 到底做了什么可以 发出信号 以及 dispatch_semaphore_wait 到底做了什么可以 一直等待着信号的发送。

dispatch_semaphore_create

从信号量的创建开始,

/*!
 * @function dispatch_semaphore_create
 *
 * @abstract
 * 用初始值创建新的计数信号量
 *
 * @discussion
 * 当两个线程需要协调时,为该值传递0是有用的 完成某一特定事件。
 * 传递一个大于零的值 用于管理有限的资源池,其中池大小相等 的价值
 * 
 * @param value
 * 信号量的起始值。传递一个小于零的值 导致返回NULL。
 * 
 *
 * @result
 * 新创建的信号量,如果失败则为NULL
 */
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);

dispatch_semaphore_signal

/*!

 * @function dispatch_semaphore_signal
 *
 * @abstract
 * Signal (increment) a semaphore.
 *
 * @discussion
 * 增加计数信号量。如果之前的值小于零
 * 这个函数在返回之前唤醒一个等待线程
 *
 * @param dsema The counting semaphore.
 * 在这个参数中传递NULL的结果是未定义的
 *
 * @result
 * 如果线程被唤醒,这个函数返回非零值。否则,返回零
 * 
 */


intptr_t
dispatch_semaphore_signal(dispatch_semaphore_t dsema)
{
    // +1 的操作
    long value = os_atomic_inc2o(dsema, dsema_value, release);
    if (likely(value > 0)) {
	return 0;
    }
    
    // wait 操作过多 和signal信号不平衡 抛出异常
    if (unlikely(value == LONG_MIN)) {
	DISPATCH_CLIENT_CRASH(value,
				"Unbalanced call to dispatch_semaphore_signal()");
    }
    //因为是基于pthread下层的封装,为了避免下层的异常,这里做异常处理
    return _dispatch_semaphore_signal_slow(dsema);
}

dispatch_semaphore_wait

/*!
 * @function dispatch_semaphore_wait
 *
 * @abstract
 * 等待(递减)信号量
 *
 * @discussion
 * 递减计数信号量。如果结果值小于零, 
 * 这个函数在返回之前等待一个信号出现。
 *
 * @param dsema
 * 信号量。在这个参数中传递NULL的结果是未定义的
 *
 * @param timeout
 * 何时超时(参见dispatch_time)。为了方便,
 * 有 DISPATCH_TIME_NOW和DISPATCH_TIME_FOREVER常量。
 *
 * @result
 * 成功时返回零,如果超时则返回非零
 */

intptr_t
dispatch_semaphore_wait(dispatch_semaphore_t dsema, dispatch_time_t timeout)
{
    // -1 操作
    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 intptr_t
_dispatch_semaphore_wait_slow(dispatch_semaphore_t dsema,
		dispatch_time_t timeout)
{
    long orig;

    _dispatch_sema4_create(&dsema->dsema_sema, _DSEMA4_POLICY_FIFO);
        
    //判断timeout
    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_cmpxchgv2o(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_sema4_wait

void
_dispatch_sema4_wait(_dispatch_sema4_t *sema)
{
	int ret = 0;
	do {
		ret = sem_wait(sema);
	} while (ret == -1 && errno == EINTR);
	DISPATCH_SEMAPHORE_VERIFY_RET(ret);
}

这里是一个do{} while()循环。 所以是对 while 条件对判断, 会在这里等到信号量的回来,然后再执行等待后面的任务处理。 这也就解释了在等待的时候,没有对信号量进行释放是不会执行后面的任务的。

猜你喜欢

转载自juejin.im/post/6996486000809607181