这是我参与8月更文挑战的第15天,活动详情查看:8月更文挑战
写在前面: iOS底层原理探究是本人在平时的开发和学习中不断积累的一段进阶之
路的。 记录我的不断探索之旅,希望能有帮助到各位读者朋友。
目录如下:
- iOS 底层原理探索 之 alloc
- iOS 底层原理探索 之 结构体内存对齐
- iOS 底层原理探索 之 对象的本质 & isa的底层实现
- iOS 底层原理探索 之 isa - 类的底层原理结构(上)
- iOS 底层原理探索 之 isa - 类的底层原理结构(中)
- iOS 底层原理探索 之 isa - 类的底层原理结构(下)
- iOS 底层原理探索 之 Runtime运行时&方法的本质
- iOS 底层原理探索 之 objc_msgSend
- iOS 底层原理探索 之 Runtime运行时慢速查找流程
- iOS 底层原理探索 之 动态方法决议
- iOS 底层原理探索 之 消息转发流程
- iOS 底层原理探索 之 应用程序加载原理dyld (上)
- iOS 底层原理探索 之 应用程序加载原理dyld (下)
- iOS 底层原理探索 之 类的加载
- iOS 底层原理探索 之 分类的加载
- iOS 底层原理探索 之 关联对象
- iOS底层原理探索 之 魔法师KVC
- iOS底层原理探索 之 KVO原理|8月更文挑战
- iOS底层原理探索 之 重写KVO|8月更文挑战
- iOS底层原理探索 之 多线程原理|8月更文挑战
- iOS底层原理探索 之 GCD函数和队列
- iOS底层原理探索 之 GCD原理(上)
- iOS底层 - 关于死锁,你了解多少?
- iOS底层 - 单例 销毁 可否 ?
- iOS底层 - Dispatch Source
- 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
就算任务一先添加,任务二耗时更加严重,但是,仍然先执行任务二
//自定义并发队列
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_signal
和 dispatch_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 条件对判断, 会在这里等到信号量的回来,然后再执行等待后面的任务处理。 这也就解释了在等待的时候,没有对信号量进行释放是不会执行后面的任务的。