中小型网站建设公司百度广告标识
一、什么是信号量?
信号量(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_tdispatch_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_SEMoffset += dsnprintf(&buf[offset], bufsiz - offset, "port = 0x%u, ",dsema->dsema_sema);
#endifoffset += 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_valueDISPATCH_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 / 2dispatch_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]);// 信号量+1dispatch_semaphore_signal(sem);});NSLog(@"主线程1");// 信号量-1dispatch_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]);// 信号量+1dispatch_semaphore_signal(sem);});NSLog(@"主线程2");// 信号量-1dispatch_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 {// 创建一个信号量,大小为0dispatch_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]);// 信号量+1dispatch_semaphore_signal(sem);});});NSLog(@"主线程1");// 信号量-1dispatch_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]);// 信号量+1dispatch_semaphore_signal(sem);});});NSLog(@"主线程2");// 信号量-1dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);dispatch_async(dispatch_get_main_queue(), ^{// 主线程刷新UINSLog(@"主线程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(), ^{// 主线程刷新UINSLog(@"主线程3,主线程刷新UI");});// 输出结果:主线程1、主线程2、无序输出:任务1、任务2,最后输出:主线程3,主线程刷新UI
}
方法三:(并行队列 + 异步组) + 信号量
#pragma mark - 方法三:(并行队列 + 异步组) + 信号量
// 与方法一类似,只是将 异步 换成了 异步组
// 任务按照顺序执行,完成后,再刷新UI
- (void)action3 {// 创建一个信号量,大小为0dispatch_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]);// 信号量+1dispatch_semaphore_signal(sem);});});NSLog(@"主线程1");// 信号量-1dispatch_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]);// 信号量+1dispatch_semaphore_signal(sem);});});NSLog(@"主线程2");// 信号量-1dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);dispatch_group_notify(group, dispatch_get_main_queue(), ^{// 主线程刷新UINSLog(@"主线程3,主线程刷新UI");});// 输出结果:主线程1、任务1、主线程2、任务2、主线程3,主线程刷新UI
}
整理完成,编辑不易,转载请注明出处,谢谢~~~