当前位置: 首页 > news >正文

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
}

整理完成,编辑不易,转载请注明出处,谢谢~~~

相关文章:

  • JavaScript 系列之:垃圾回收机制
  • zotero:如何快捷使用硅基流动(siliconflow)的deepseek?
  • 27.贪心算法5
  • DeepSeek开源周,第四弹再次来袭,优化并行策略
  • cesium 解决加载带动画的glb不播放动画问题
  • 记录MFC联合halcon界面显示开发
  • 查询NFT图片地址
  • 42 session反序列化漏洞
  • JavaWeb25.02.27
  • 【前端】XML,XPATH,与HTML的关系
  • 推荐算法工程师的技术图谱和学习路径
  • 实验环境搭建集锦(docker linux ros2+强化学习环境+linux上单片机串口调试)
  • Kylin麒麟操作系统服务部署 | Nginx服务部署
  • BRD4缺失通过GRP78灭活内质网应激,延缓脱氢表雄酮诱导的卵巢颗粒细胞凋亡
  • 波导阵列天线 学习笔记11双极化全金属垂直公共馈电平板波导槽阵列天线
  • 【技术笔记】Cadence 实现原理图元器件的自动标号
  • [教程]在CentStream 9简单部署一个Nginx web服务器
  • 基于多层感知机(MLP)实现MNIST手写体识别
  • SpringBoot Maven 项目 pom 中的 plugin 插件用法整理
  • [高等数学] 定积分的概念与性质
  • 怎么建立免费个人网站/网络营销的功能有哪些?
  • 做logo的著名网站/知名seo公司
  • 怎么看一个网站是谁做的/优化关键词是什么意思
  • t字型布局的网站在dw怎么做/中国突然宣布大消息
  • wordpress 静态地址/百度上海推广优化公司
  • 网站如何做收款二维码/网站排名优化推广