iOS开发之GCD信号量dispatch_semaphore_t源码分析和使用
一、什么是信号量?
信号量(Semaphore)是一种用于同步和互斥的机制,主要用于控制对共享资源的访问。它是一个整型变量,可以通过特定的操作进行访问和修改。信号量的值可以是0、1或者n,表示可用资源的数量。
1、信号量的基本概念和作用
信号量可以理解为一个计数器,用于表示可用资源的数量。它通过两个主要操作来实现同步和互斥:
(1)P(等待)操作:当一个进程或线程需要访问共享资源时,它会尝试执行P操作。如果信号量的值大于0,则进程可以继续访问资源,并将信号量的值减1;如果信号量的值等于0,则进程会被阻塞,直到信号量的值变为正数。
(2)V(释放)操作:当一个进程或线程完成对共享资源的访问时,它会执行V操作,将信号量的值加1。如果有其他等待进程被阻塞,它们中的一个将被唤醒并获得对资源的访问权限。
二、3个方法和源码分析
iOS中,信号量对于允许多个线程并发访问的资源,它是一个很好的选择。一个初始值为N的信号量允许N+1个线程并发访问(数字是从0开始的)。线程访问资源时,首先获取信号量,即创建信号量,然后等待将信号量的值减1。如果信号量的值小于0,则进入等待状态,否则继续执行。访问资源之后,线程释放信号量,将信号量的值加1。如果信号量的值不小于0,唤醒一个等待中的线程。
1、创建具有初始值的新计数信号量
/*!
* @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.
* 当两个线程需要协调特定事件的完成时,值传递零非常有用。传递大于零的值对于管理有限资源池非常有用,其中池大小等于该值。(实际是该值+1)
*
*/
dispatch_semaphore_t
dispatch_semaphore_create(intptr_t value);
2、等待(递减)信号量
/*!
* @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. If the timeout is
* reached without a signal being received, the semaphore is re-incremented
* before the function returns.
* 减少计数信号量。如果结果值小于零,他的函数会在返回之前等待信号出现。如果到达超时而没有收到信号,则在函数返回之前,信号量会重新递增。
*
*/
intptr_t
dispatch_semaphore_wait(dispatch_semaphore_t dsema, dispatch_time_t timeout);
3、释放信号量(增量)
/*!
* @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.
* 增加计数信号量。如果前一个值小于零,他的函数会在返回之前唤醒一个等待的线程。
*
*/
intptr_t
dispatch_semaphore_signal(dispatch_semaphore_t dsema);
4、源码分析(源码地址:https://github.com/swiftlang/swift-corelibs-libdispatch/blob/main/src/semaphore.c)
#pragma mark -
#pragma mark dispatch_semaphore_t
dispatch_semaphore_t
dispatch_semaphore_create(intptr_t 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;
}
void
_dispatch_semaphore_dispose(dispatch_object_t dou,
DISPATCH_UNUSED bool *allow_free)
{
// 信号量销毁,销毁时,会判断信号量的值dsema_value和原始信号量的值dsema_orig是否相等,如果dsema_value<dsema_orig,那么系统就会认为信号量还在使用中,此时销毁就会发生crash。所以这也是dispatch_semaphore_signal和dispatch_semaphore_wait要成对使用的原因。
dispatch_semaphore_t dsema = dou._dsema;
if (dsema->dsema_value < dsema->dsema_orig) {
DISPATCH_CLIENT_CRASH(dsema->dsema_orig - dsema->dsema_value,
"Semaphore object deallocated while in use");
}
_dispatch_sema4_dispose(&dsema->dsema_sema, _DSEMA4_POLICY_FIFO);
}
size_t
_dispatch_semaphore_debug(dispatch_object_t dou, char *buf, size_t bufsiz)
{
dispatch_semaphore_t dsema = dou._dsema;
size_t offset = 0;
offset += dsnprintf(&buf[offset], bufsiz - offset, "%s[%p] = { ",
_dispatch_object_class_name(dsema), dsema);
offset += _dispatch_object_debug_attr(dsema, &buf[offset], bufsiz - offset);
#if USE_MACH_SEM
offset += dsnprintf(&buf[offset], bufsiz - offset, "port = 0x%u, ",
dsema->dsema_sema);
#endif
offset += dsnprintf(&buf[offset], bufsiz - offset,
"value = %" PRIdPTR ", orig = %" PRIdPTR " }", dsema->dsema_value, dsema->dsema_orig);
return offset;
}
DISPATCH_NOINLINE
intptr_t
_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;
}
intptr_t
dispatch_semaphore_signal(dispatch_semaphore_t dsema)
{
// 调用os_atomic_inc2o->os_atomic_add2o->os_atomic_add->_os_atomic_c11_op((p), (v), m, add, +)->atomic_fetch_add_explicit,释放资源(release),对信号量加1,返回信号量的值,如果 值>0,未阻塞,正常返回;否则,阻塞,调用函数_dispatch_semaphore_signal_slow,发送一个信号去唤起等待的线程。
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_NOINLINE
static intptr_t
_dispatch_semaphore_wait_slow(dispatch_semaphore_t dsema,
dispatch_time_t timeout)
{
long orig;
// 三种超时情况
// 1、DISPATCH_TIME_NOW,则表示不等待,这时候会再次尝试获取信号量,如果信号量此时已然小于0,则返回超时提醒,并将信号量的值+1;
// 2、DISPATCH_TIME_FOREVER,表示会一直等待被唤醒,调用函数_dispatch_sema4_wait进行等待;
// 3、如果是给定的一段时间,走default逻辑,调用函数_dispatch_sema4_timedwait进入计时等待,等线程被唤醒后,返回结果,如果到时间未被唤醒,返回超时;
_dispatch_sema4_create(&dsema->dsema_sema, _DSEMA4_POLICY_FIFO);
switch (timeout) {
default:
if (!_dispatch_sema4_timedwait(&dsema->dsema_sema, timeout)) {
break;
}
// Try to undo what the fast path did to dsema->dsema_value
DISPATCH_FALLTHROUGH;
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(). Drain the wakeup.
DISPATCH_FALLTHROUGH;
case DISPATCH_TIME_FOREVER:
_dispatch_sema4_wait(&dsema->dsema_sema);
break;
}
return 0;
}
intptr_t
dispatch_semaphore_wait(dispatch_semaphore_t dsema, dispatch_time_t timeout)
{
// 调用os_atomic_dec2o->os_atomic_sub2o->os_atomic_sub->_os_atomic_c11_op((p), (v), m, sub, -)->atomic_fetch_sub_explicit,原子操作将当前信号量的值进行-1,并返回值,如果 值>=0,表示有资源可用,正常返回,否则调用_dispatch_semaphore_wait_slow方法进行等待
long value = os_atomic_dec2o(dsema, dsema_value, acquire);
if (likely(value >= 0)) {
return 0;
}
return _dispatch_semaphore_wait_slow(dsema, timeout);
}
三、方法的基础测试和使用
1、串行队列 + 异步 == 只会开启一个子线程,且队列中所有的任务都是在这个子线程中执行
#pragma mark - 串行队列 + 异步 == 只会开启一个子线程,且队列中所有的任务都是在这个子线程中执行
- (void)test1 {
// 串行队列
dispatch_queue_t queue = dispatch_queue_create("serial", DISPATCH_QUEUE_SERIAL);
dispatch_async(queue, ^{
// 模拟复杂操作
int i = 0;
for (int j = 0; j < 100000000; j++) {
i += 1;
}
NSLog(@"任务1:%@",[NSThread currentThread]);
});
NSLog(@"主线程1");
dispatch_async(queue, ^{
// 模拟复杂操作
int i = 0;
for (int j = 0; j < 100000000; j++) {
i += 1;
}
NSLog(@"任务2:%@",[NSThread currentThread]);
});
NSLog(@"主线程2");
dispatch_async(queue, ^{
// 模拟复杂操作
int i = 0;
for (int j = 0; j < 100000000; j++) {
i += 1;
}
NSLog(@"任务3:%@",[NSThread currentThread]);
});
NSLog(@"主线程3");
// 输出结果:主线程1、主线程2、主线程3,同一个子线程,顺序输出:任务1、任务2、任务3
}
2、并行队列 + 异步,会开启不同的子线程,且子线程无序执行
#pragma mark - 并行队列 + 异步,会开启不同的子线程,且子线程无序执行
// 一定要添加“模拟复杂操作”,否则简单的一个输出语句,无法验证“先主,后异步block无序输出”此结果的正确性,
// 猜测:简单的一个输出语句,处理时间太快,时间四舍五入后,输出的结果与理论上的“先主,后异步block无序输出”
// 的结果不一致
- (void)test2 {
// 并行队列
dispatch_queue_t queue = dispatch_queue_create("concurrent", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queue, ^{
// 模拟复杂操作
int i = 0;
for (int j = 0; j < 100000000; j++) {
i += 1;
}
NSLog(@"任务1:%@",[NSThread currentThread]);
});
NSLog(@"主线程1");
dispatch_async(queue, ^{
// 模拟复杂操作
int i = 0;
for (int j = 0; j < 100000000; j++) {
i += 1;
}
NSLog(@"任务2:%@",[NSThread currentThread]);
});
NSLog(@"主线程2");
dispatch_async(queue, ^{
// 模拟复杂操作
int i = 0;
for (int j = 0; j < 100000000; j++) {
i += 1;
}
NSLog(@"任务3:%@",[NSThread currentThread]);
});
NSLog(@"主线程3");
// 输出结果:主线程1、主线程2、主线程3,不同子线程无序输出:任务1、任务2、任务3
}
3、并行队列 + 异步,用GCD的信号量来实现子线程顺序执行
#pragma mark - 并行队列 + 异步,用GCD的信号量来实现子线程顺序执行
- (void)test3 {
// 创建一个信号量,大小为0 / 1 / 2
dispatch_semaphore_t sem = dispatch_semaphore_create(0);
// 并行队列
dispatch_queue_t queue = dispatch_queue_create("concurrent", DISPATCH_QUEUE_CONCURRENT);// dispatch_get_global_queue(0, 0)
dispatch_async(queue, ^{
// 模拟复杂操作
int i = 0;
for (int j = 0; j < 100000000; j++) {
i += 1;
}
NSLog(@"任务1:%@",[NSThread currentThread]);
// 信号量+1
dispatch_semaphore_signal(sem);
});
NSLog(@"主线程1");
// 信号量-1
dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);
dispatch_async(queue, ^{
// 模拟复杂操作
int i = 0;
for (int j = 0; j < 100000000; j++) {
i += 1;
}
NSLog(@"任务2:%@",[NSThread currentThread]);
// 信号量+1
dispatch_semaphore_signal(sem);
});
NSLog(@"主线程2");
// 信号量-1
dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);
dispatch_async(queue, ^{
// 模拟复杂操作
int i = 0;
for (int j = 0; j < 100000000; j++) {
i += 1;
}
NSLog(@"任务3:%@",[NSThread currentThread]);
});
NSLog(@"主线程3");
// 信号量大小 == 0 时:
// 输出结果:主线程1、任务1、主线程2、任务2、主线程3、任务3,在同一个子线程中输出任务,如果
// 使用的是全局队列,可能不是在同一个子线程中输出任务
// 信号量大小 == 1 时:
// 输出结果:主线程1、主线程2,无序输出:任务1、任务2、主线程3,但 主线程3 永远不会在无序的第一个,
// 任务3 永远在最后输出,在不同子线程中输出任务
// 信号量大小 >=2 时:
// 输出结果:主线程1、主线程2、主线程3,不同子线程无序输出:任务1、任务2、任务3,相当于没有添加信号量
// 根据以上测试结果可以更好的理解,
// 创建信号量:dispatch_semaphore_create()、
// 信号量+1:dispatch_semaphore_signal()、
// 信号量-1:dispatch_semaphore_wait()
// 这三个方法的含义。
}
四、信号量的场景应用
信号量的应用场景:1、两个请求结果都返回后,再刷新UI;2、并发处理同一个数据;
注:异步block中嵌入另一个异步block模拟数据请求
方法一:(并行队列 + 异步) + 信号量
#pragma mark - 方法一:(并行队列 + 异步) + 信号量
// 任务按照顺序执行,完成后,再刷新UI
- (void)action1 {
// 创建一个信号量,大小为0
dispatch_semaphore_t sem = dispatch_semaphore_create(0);
// 并行队列
dispatch_queue_t queue = dispatch_queue_create("concurrent", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queue, ^{
// 模拟数据请求
dispatch_async(queue, ^{
int i = 0;
for (int j = 0; j < 100000000; j++) {
i += 1;
}
NSLog(@"任务1:%@",[NSThread currentThread]);
// 信号量+1
dispatch_semaphore_signal(sem);
});
});
NSLog(@"主线程1");
// 信号量-1
dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);
dispatch_async(queue, ^{
// 模拟数据请求
dispatch_async(queue, ^{
int i = 0;
for (int j = 0; j < 100000000; j++) {
i += 1;
}
NSLog(@"任务2:%@",[NSThread currentThread]);
// 信号量+1
dispatch_semaphore_signal(sem);
});
});
NSLog(@"主线程2");
// 信号量-1
dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);
dispatch_async(dispatch_get_main_queue(), ^{
// 主线程刷新UI
NSLog(@"主线程3,主线程刷新UI");
});
// 输出结果:主线程1、任务1、主线程2、任务2、主线程3,主线程刷新UI
}
方法二:(并行队列 + 异步组) + dispatch_group_enter、leave(group)对
#pragma mark - 方法二:(并行队列 + 异步组) + dispatch_group_enter、leave(group)对
// 如果不加”dispatch_group_enter、leave(group)对“,输出结果是无序的,
// 且无法得到”两个请求结果都返回后,再刷新UI“的效果。
// 任务按照无序执行,完成后,再刷新UI
- (void)action2 {
// 并行队列
dispatch_queue_t queue = dispatch_queue_create("concurrent", DISPATCH_QUEUE_CONCURRENT);
// 组
dispatch_group_t group = dispatch_group_create();
// 进入组
dispatch_group_enter(group);
dispatch_group_async(group, queue, ^{
// 模拟数据请求
dispatch_async(queue, ^{
int i = 0;
for (int j = 0; j < 100000000; j++) {
i += 1;
}
NSLog(@"任务1:%@",[NSThread currentThread]);
// 离开组
dispatch_group_leave(group);
});
});
NSLog(@"主线程1");
// 进入组
dispatch_group_enter(group);
dispatch_group_async(group, queue, ^{
// 模拟数据请求
dispatch_async(queue, ^{
int i = 0;
for (int j = 0; j < 100000000; j++) {
i += 1;
}
NSLog(@"任务2:%@",[NSThread currentThread]);
// 离开组
dispatch_group_leave(group);
});
});
NSLog(@"主线程2");
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
// 主线程刷新UI
NSLog(@"主线程3,主线程刷新UI");
});
// 输出结果:主线程1、主线程2、无序输出:任务1、任务2,最后输出:主线程3,主线程刷新UI
}
方法三:(并行队列 + 异步组) + 信号量
#pragma mark - 方法三:(并行队列 + 异步组) + 信号量
// 与方法一类似,只是将 异步 换成了 异步组
// 任务按照顺序执行,完成后,再刷新UI
- (void)action3 {
// 创建一个信号量,大小为0
dispatch_semaphore_t sem = dispatch_semaphore_create(0);
// 并行队列
dispatch_queue_t queue = dispatch_queue_create("concurrent", DISPATCH_QUEUE_CONCURRENT);
// 组
dispatch_group_t group = dispatch_group_create();
dispatch_group_async(group, queue, ^{
// 模拟数据请求
dispatch_async(queue, ^{
int i = 0;
for (int j = 0; j < 100000000; j++) {
i += 1;
}
NSLog(@"任务1:%@",[NSThread currentThread]);
// 信号量+1
dispatch_semaphore_signal(sem);
});
});
NSLog(@"主线程1");
// 信号量-1
dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);
dispatch_group_async(group, queue, ^{
// 模拟数据请求
dispatch_async(queue, ^{
int i = 0;
for (int j = 0; j < 100000000; j++) {
i += 1;
}
NSLog(@"任务2:%@",[NSThread currentThread]);
// 信号量+1
dispatch_semaphore_signal(sem);
});
});
NSLog(@"主线程2");
// 信号量-1
dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
// 主线程刷新UI
NSLog(@"主线程3,主线程刷新UI");
});
// 输出结果:主线程1、任务1、主线程2、任务2、主线程3,主线程刷新UI
}
整理完成,编辑不易,转载请注明出处,谢谢~~~