一、栅栏函数

  • 栅栏函数最直接的作用是:控制任务执行顺序
  • dispatch_barrier_async:前面的任务执行完毕才会来到这里
  • dispatch_barrier_sync: 前面的任务执行完毕才会来到这里,但是这个会堵塞线程,影响后面的任务执行.
  • 非常重要的一点是: 栅栏函数只能控制同一并发队列.

1.1 dispatch_barrier_async

  • 查看一个加载案例、猜测它的执行结果.
- (void)dispatch_barrier_async_test{dispatch_queue_t concurrentQueue = dispatch_queue_create("holo", DISPATCH_QUEUE_CONCURRENT);dispatch_async(concurrentQueue, ^{//sleep(2);NSLog(@"dispatch async 1");});dispatch_barrier_async(concurrentQueue, ^{NSLog(@"dispatch barrier async %@",[NSThread currentThread]);});dispatch_async(concurrentQueue, ^{NSLog(@"dispatch async 2");});// sleep(3);NSLog(@"main queue log");
}
  • 看效果做结论、先看效果、可以看到执行的结果为
2021-08-23 16:20:57.208602+0800 003---GCD应用[28932:2356823] main queue log
2021-08-23 16:20:57.208622+0800 003---GCD应用[28932:2356901] dispatch async 1
2021-08-23 16:20:57.208918+0800 003---GCD应用[28932:2356901] dispatch barrier async <NSThread: 0x60000330c040>{number = 7, name = (null)}
2021-08-23 16:20:57.209059+0800 003---GCD应用[28932:2356901] dispatch async 2
  • 由于注释了sleep函数、此时可以看到 main queue log执行较快、在 dispatch async 1之前,不可作为执行依据、稍后验证.
  • 但是由于设置了 dispatch_barrier_async,可以看到它在1之后执行,执行完barrier之后再执行了2, 相互之间按照顺序异步执行.
  • 此时将1中的sleep(2)函数放开、再观察结果、会发现依然是按照顺序执行;
2021-08-23 16:26:58.991751+0800 003---GCD应用[28970:2360562] main queue log
2021-08-23 16:27:00.994112+0800 003---GCD应用[28970:2360640] dispatch async 1
2021-08-23 16:27:04.999322+0800 003---GCD应用[28970:2360640] dispatch barrier async <NSThread: 0x600001d15f40>{number = 2, name = (null)}
2021-08-23 16:27:04.999521+0800 003---GCD应用[28970:2360640] dispatch async 2
  • 这个时候如果再增加两个延迟时间,可以看到main queue log 的执行是没有规律的、
- (void)dispatch_barrier_async_test{dispatch_queue_t concurrentQueue = dispatch_queue_create("holo", DISPATCH_QUEUE_CONCURRENT);dispatch_async(concurrentQueue, ^{sleep(2);NSLog(@"dispatch async 1");});dispatch_barrier_async(concurrentQueue, ^{sleep(4);NSLog(@"dispatch barrier async %@",[NSThread currentThread]);});dispatch_async(concurrentQueue, ^{NSLog(@"dispatch async 2");});sleep(3);NSLog(@"main queue log");
}
  • 由于当前是异步函数、最后一句main queue log的执行与调度有关、所以它是无序的,有序的是设置了异步栅栏函数的地方.
2021-08-23 16:30:11.252698+0800 003---GCD应用[28993:2362452] dispatch async 1
2021-08-23 16:30:12.249849+0800 003---GCD应用[28993:2362375] main queue log
2021-08-23 16:30:15.257561+0800 003---GCD应用[28993:2362454] dispatch barrier async <NSThread: 0x6000009d0d80>{number = 7, name = (null)}
2021-08-23 16:30:15.257764+0800 003---GCD应用[28993:2362454] dispatch async 2
  • dispatch_barrier_async执行之前再增加异步函数,在栅栏函数内增加数秒延迟,依然可以得到任务执行顺序:
  • dispatch async 1
  • dispatch barrier async <NSThread: 0x6000009d0d80>{number = 7, name = (null)}
  • dispatch async 2
  • 当异步栅栏函数前面的任务执行结果返回之前,不会执行异步栅栏函数的任务;异步栅栏函数的任务执行结果没有返回的时候,异步栅栏函数之后的任务不会执行

1.2 dispatch_barrier_sync

  • 根据上边的异步栅栏函数、我们这个时候来看同步栅栏函数、做对比、即可得到它的执行结论.案例为:自定义并发队列
- (void)dispatch_barrier_sync_test{dispatch_queue_t concurrentQueue = dispatch_queue_create("holo", DISPATCH_QUEUE_CONCURRENT);dispatch_async(concurrentQueue, ^{sleep(2);NSLog(@"dispatch async 1");});dispatch_async(concurrentQueue, ^{sleep(4);NSLog(@"dispatch async 2");});dispatch_barrier_sync(concurrentQueue, ^{sleep(1);NSLog(@"dispatch barrier async %@",[NSThread currentThread]);});dispatch_async(concurrentQueue, ^{NSLog(@"dispatch async 3");});//sleep(1); //执行此次会发现它与3相互之间无序执行.NSLog(@"main queue log");
}
  • 执行结果如下
2021-08-23 16:49:06.811172+0800 003---GCD应用[29165:2375765] dispatch async 1
2021-08-23 16:49:08.811608+0800 003---GCD应用[29165:2375763] dispatch async 2
2021-08-23 16:49:09.812001+0800 003---GCD应用[29165:2375688] dispatch barrier async <NSThread: 0x600000d705c0>{number = 1, name = main}
2021-08-23 16:49:09.812189+0800 003---GCD应用[29165:2375688] main queue log
2021-08-23 16:49:09.812220+0800 003---GCD应用[29165:2375763] dispatch async 3
  • 当是同步栅栏函数时,main queue log会根据前面任务的执行时间来决定自己处于什么顺序执行,但是现在即使没有添加延迟时间,它也不会执行了,意味着: 同步栅栏函数会堵塞后续当前队列和主队列任务的执行
  • 那么同步栅栏函数的执行结论为:
    当同步栅栏函数前面的任务执行结果返回之前,不会执行同步栅栏函数的任务;同步栅栏函数的任务执行结果没有返回的时候,同步栅栏函数之后的任务不会执行,并且同步栅栏函数会堵塞后续当前队列和主队列任务的执行.

1.3 dispatch_barrier_sync/dispatch_barrier_async坑点

1.3.1 全局并发队列坑点

  • 当我们使用自定义并发队列时可以看到栅栏函数实现我们的预期执行顺序.实战场景为:可以在栅栏函数之前设置图片的获取,然后在栅栏函数中设置图片的水印.
  • 但是当我们使用全局并发队列时,这个执行顺序就被打破了.可以修改一下1.2章节案例自定义并发队列执行的concurrentQueue为全局并发队列、我们查看下执行顺序.
2021-08-23 17:04:01.803862+0800 003---GCD应用[29269:2383257] dispatch barrier async <NSThread: 0x600001d604c0>{number = 1, name = main}
2021-08-23 17:04:01.804073+0800 003---GCD应用[29269:2383344] dispatch async 3
2021-08-23 17:04:02.804884+0800 003---GCD应用[29269:2383257] main queue log
2021-08-23 17:04:03.807569+0800 003---GCD应用[29269:2383345] dispatch async 1
2021-08-23 17:04:05.808487+0800 003---GCD应用[29269:2383346] dispatch async 2
  • 这个时候因为延迟时间的“加持”,很想当然的就看到了这样一个“理想的执行顺序”,显然:栅栏同步函数的特性被全局并发队列打破.不仅如此,当修改了异步栅栏函数的队列为全局并发队列时,异步栅栏函数的执行顺序也被打乱;

1.3.2 线程安全问题

  • 假设当前需要遍历1000遍、加载本地的10张图片,然后添加到一个数组中,此时案例如下:
- (void)dispatch_queue_safety{NSMutableArray *imageArr = [NSMutableArray array];dispatch_queue_t queue = dispatch_queue_create("holo", DISPATCH_QUEUE_CONCURRENT);for (int i = 0; i < 1000; i++) {dispatch_async(queue, ^{NSString *imageName = [NSString stringWithFormat:@"%d.jpg",(i % 10)];NSURL *url = [[NSBundle mainBundle]URLForResource:imageName withExtension:nil];NSData *data = [NSData dataWithContentsOfURL: url];UIImage *image = [UIImage imageWithData:data];[imageArr addObject:image];});}
}
  • 在运行的过程中、可能短时间不会发生崩溃、但是反复运行几次之后,会发现如下闪退信息.
  • 此时表明读写过程中数据不安全,会出现空值的情况、随便对i做下条件控制,查看下其中的数据即可看到、
  • 产生的原因:
    • 不断的对imageArr做数据读写操作. 在写入的过程中是对旧值的realease,新值的retain,由于当前是多线程操作,在某一个过程中,我们对数据做了多次旧值的release操作,还没来得及匹配上新值的retain操作, 就要对新的数据读取,此时读取到的是一个野指针.就会产生读写异常.
  • 针对上述情况、我们可以使用异步栅栏函数dispatch_barrier_async对数组的读写操作进行控制,从而达到线程安全的目的,
dispatch_barrier_async(queue, ^{[imageArr addObject:image];if(i == 999){NSLog(@"%@",imageArr);}
});
  • dispatch_barrier_sync不可取,会对后续的循环操作产生堵塞,造成程序死锁

1.4 栅栏函数底层原理

  • 为了探究为什么使用全局并发队列就会破坏栅栏函数的执行特性?

  • 我们来分析一下dispatch_barrier_sync函数的底层、并且根据调用函数依次可以查到执行顺序的函数为

  • 1.dispatch_barrier_sync

  • 2._dispatch_barrier_sync_f

  • 3._dispatch_barrier_sync_f_inline

    • 3.1 _dispatch_sync_f_slow
    • 3.2 _dispatch_sync_recurse
      • 3.2.1 _dispatch_sync_f_slow
      • 3.2.2 _dispatch_introspection_sync_begin
      • 3.2.3 _dispatch_sync_invoke_and_complete_recurse
        • 3.2.3.1 _dispatch_sync_function_invoke_inline

          • 3.2.3.1.1 _dispatch_client_callout

            • 3.2.3.1.1.1 f(ctxt)
        • 3.2.3.2 _dispatch_sync_complete_recurse
          • 3.2.3.2.1 _dispatch_lane_wakeup
          • 3.2.3.2.2 _dispatch_lane_non_barrier_complete
    • 3.3 _dispatch_introspection_sync_begin
    • 3.4 _dispatch_lane_barrier_sync_invoke_and_complete
  • 到此时、可以看到在分析同步函数的时候底层也会调用_dispatch_sync_f_slow函数,那么此处除了在外层的不同之处外,底层调用函数高度一致.

  • 这里为了防止在 3.2 _dispatch_sync_recurse函数中迷失,我们回到问题的本质:为什么全局并发队列不能配合栅栏函数使用,为什么栅栏函数可以执行"栅栏"的功能特性?

  • 这里我们来到同步递归函数 3.2 _dispatch_sync_recurse

static void
_dispatch_sync_recurse(dispatch_lane_t dq, void *ctxt,dispatch_function_t func, uintptr_t dc_flags){//1.获取当前线程iddispatch_tid tid = _dispatch_tid_self();dispatch_queue_t tq = dq->do_targetq;//2.进入do-while死循环.do {    //2.1 判断当前是否为串行队列if (likely(tq->dq_width == 1)) {if (unlikely(!_dispatch_queue_try_acquire_barrier_sync(tq, tid))) {//执行_dispatch_sync_f_slow函数的最终也就是执行了函数的调用 func(ctxt);return _dispatch_sync_f_slow(dq, ctxt, func, dc_flags, tq,DC_FLAG_BARRIER);}} else {//2.2 并发队列: 递归回到_dispatch_sync_f_slow函数dispatch_queue_concurrent_t dl = upcast(tq)._dl;if (unlikely(!_dispatch_queue_try_reserve_sync_width(dl))) {//继续递归执行栅栏函数之前的任务return _dispatch_sync_f_slow(dq, ctxt, func, dc_flags, tq, 0);}}tq = tq->do_targetq;} while (unlikely(tq->do_targetq));//3. 上述do while循环结束后开始执行同步函数的任务_dispatch_introspection_sync_begin(dq);//4. 同步调用和递归完成_dispatch_sync_invoke_and_complete_recurse(dq, ctxt, func, dc_flagsDISPATCH_TRACE_ARG(_dispatch_trace_item_sync_push_pop(dq, ctxt, func, dc_flags)));
}
  • 3.2 _dispatch_sync_recurse函数的作用:
  • 要想执行dispatch_barrier_sync中的任务,必须先讲同步栅栏函数任务之前的任务清空,也就是:进入do-while循环.递归清空当前栅栏函数中的任务,
  • 3.2.3_dispatch_sync_invoke_and_complete_recurse函数的作用是执行函数的调用并且完成递归函数的调用.
static void
_dispatch_sync_invoke_and_complete_recurse(dispatch_queue_class_t dq,void *ctxt, dispatch_function_t func, uintptr_t dc_flagsDISPATCH_TRACE_ARG(void *dc)){//1.此处执行函数的调用操作_dispatch_sync_function_invoke_inline(dq, ctxt, func);_dispatch_trace_item_complete(dc);//2.此处执行同步递归操作:去唤醒执行栅栏函数之前的所有任务,执行完毕之后做标记._dispatch_sync_complete_recurse(dq._dq, NULL, dc_flags);
}
  • 3.2.3.2 _dispatch_sync_complete_recurse函数的内部实现
static void
_dispatch_sync_complete_recurse(dispatch_queue_t dq, dispatch_queue_t stop_dq,uintptr_t dc_flags){//1.根据标记判断当前是否有栅栏函数之前的任务的执行bool barrier = (dc_flags & DC_FLAG_BARRIER);do {//2. 进入do-while循环、做栅栏函数之前的任务的处理//2.1 如果当前经过_dispatch_lane_non_barrier_complete栅栏函数的任务执行完毕并且做了标记、就直接return.if (dq == stop_dq) return;//2.2 判断当前栅栏函数前是否还有任务、如果有就去执行wakeup唤醒操作、然后去执行if (barrier) {dx_wakeup(dq, 0, DISPATCH_WAKEUP_BARRIER_COMPLETE);} else {//2.3 如果当前栅栏函数前没有任务了,就执行该函数:做状态的改变处理._dispatch_lane_non_barrier_complete(upcast(dq)._dl, 0);}dq = dq->do_targetq;barrier = (dq->dq_width == 1);//3.栅栏函数前的任务的队列标记是串行队列、则任务还没有执行完毕.barrier=1,继续执行,} while (unlikely(dq->do_targetq));
}

_dispatch_sync_complete_recurse函数总结

  • 1.根据标记判断当前是否有栅栏函数之前的任务的执行
  • 2.进入do-while循环、做栅栏函数之前的任务的处理
    • 2.1 如果当前经过_dispatch_lane_non_barrier_complete栅栏函数的任务执行完毕并且做了标记、就直接return.
    • 2.2 判断当前栅栏函数前是否还有任务、如果有就去执行wakeup唤醒操作、然后去执行
    • 2.3 如果当前栅栏函数前没有任务了,就执行该函数:做状态的改变处理.
  • 3.栅栏函数前的任务的队列标记是串行队列、则任务还没有执行完毕.barrier=1,继续执行

自定义并发队列与全局并发队列在唤醒函数上的区别

  • 由于当前是自定义并发队列,所以我们查看的是3.2.3.2.1 _dispatch_lane_wakeup唤醒函数, 但是如果是全局并发队列,那么此处的唤醒函数为_dispatch_root_queue_wakeup;
  • 首先查看下全局并发队列的唤醒函数_dispatch_root_queue_wakeup
void _dispatch_root_queue_wakeup(dispatch_queue_global_t dq,DISPATCH_UNUSED dispatch_qos_t qos, dispatch_wakeup_flags_t flags){if (!(flags & DISPATCH_WAKEUP_BLOCK_WAIT)) {DISPATCH_INTERNAL_CRASH(dq->dq_priority, "Don't try to wake up or override a root queue");}if (flags & DISPATCH_WAKEUP_CONSUME_2) {return _dispatch_release_2_tailcall(dq);}
}
  • 全局并发队列唤醒操作下,一路探索下去、最后会与dispose释放操作有关.没有其他的特殊处理.
  • 这个时候查看自定义并发队列的唤醒函数 _dispatch_lane_wakeup
void _dispatch_lane_wakeup(dispatch_lane_class_t dqu, dispatch_qos_t qos,dispatch_wakeup_flags_t flags){//1.如果当前的队列没有处于唤醒状态dispatch_queue_wakeup_target_t target = DISPATCH_QUEUE_WAKEUP_NONE;//2.如果当前栅栏函数前的任务 标记为栅栏函数的唤醒完成if (unlikely(flags & DISPATCH_WAKEUP_BARRIER_COMPLETE)) {//执行栅栏函数任务的完成函数.return _dispatch_lane_barrier_complete(dqu, qos, flags);}//3.取出原子操作存储的状态值,如果值不为空,则进行标记if (_dispatch_queue_class_probe(dqu)) {target = DISPATCH_QUEUE_WAKEUP_TARGET;}//4.走没有加barrier的情况:同步函数或异步函数return _dispatch_queue_wakeup(dqu, qos, flags, target);
}

** _dispatch_lane_wakeup函数总结**

  • 1.如果当前的队列没有处于唤醒状态
  • 2.如果当前栅栏函数前的任务 标记为栅栏函数的唤醒完成
  • 3.取出原子操作存储的状态值,如果值不为空,则进行标记
  • 4.走没有barrier栅栏的情况:同步函数或异步函数
  • 由于当前执行的是栅栏函数、那么我们查看_dispatch_lane_barrier_complete函数.
static void
_dispatch_lane_barrier_complete(dispatch_lane_class_t dqu, dispatch_qos_t qos,dispatch_wakeup_flags_t flags){dispatch_queue_wakeup_target_t target = DISPATCH_QUEUE_WAKEUP_NONE;dispatch_lane_t dq = dqu._dl;if (dq->dq_items_tail && !DISPATCH_QUEUE_IS_SUSPENDED(dq)) {struct dispatch_object_s *dc = _dispatch_queue_get_head(dq);//1.如果当前是同步队列 或者当前调度对象是栅栏if (likely(dq->dq_width == 1 || _dispatch_object_is_barrier(dc))) {//1.1 判断当前调度对象是否处于等待状态 (DC_FLAG_SYNC_WAITER | DC_FLAG_ASYNC_AND_WAIT)if (_dispatch_object_is_waiter(dc)) {//1.1.1 进行栅栏函数的等待回调和唤醒操作_dispatch_barrier_waiter_redirect_or_wakereturn _dispatch_lane_drain_barrier_waiter(dq, dc, flags, 0);}} else if (dq->dq_width > 1 && !_dispatch_object_is_barrier(dc)) {//2.当前为并发队列且没有栅栏函数的情况:也就是执行了栅栏函数之后的并发队列的任务.return _dispatch_lane_drain_non_barriers(dq, dc, flags);}.....uint64_t owned = DISPATCH_QUEUE_IN_BARRIER + dq->dq_width * DISPATCH_QUEUE_WIDTH_INTERVAL;//3.执行栅栏函数完成的操作: 将栅栏函数执行完毕并且清空栅栏函数相关的标记操作,方便栅栏函数之后的任务正常执行.return _dispatch_lane_class_barrier_complete(dq, qos, flags, target, owned);
}

_dispatch_lane_barrier_complete函数总结

  • 1.如果当前是同步队列 或者当前调度对象是栅栏;

    • 1.1 判断当前调度对象是否处于等待状态 (DC_FLAG_SYNC_WAITER | DC_FLAG_ASYNC_AND_WAIT)

      • 1.1.1 进行栅栏函数的等待回调和唤醒操作_dispatch_barrier_waiter_redirect_or_wake
  • 2.当前为并发队列且没有栅栏函数的情况:也就是执行了栅栏函数之后的并发队列的任务.
  • 3.执行栅栏函数完成的操作: 将栅栏函数执行完毕并且清空栅栏函数相关的标记操作,方便栅栏函数之后的任务正常执行.

综上

  • 全局并发队列结合栅栏函数之所以会破坏栅栏函数的执行特性:是因为在它的唤醒操作过程中并没有对栅栏函数特殊的处理.所以就如同普通的函数调用一样执行.
  • 自定义并发队列栅栏函数之所以会有这样的执行特性:是因为内部进行了任务唤醒操作时,对栅栏函数进行了特殊的执行判断,如果栅栏函数前的任务没有执行完毕,那么do-while循环就会一直执行,直到执行完毕,然后再执行自身的栅栏函数,再之后清空栅栏函数的相关标记,执行栅栏函数之后的任务.

1.5 栅栏函数的使用问题

值得注意的是: 栅栏函数的使用问题.

  • 通过前面的案例,我们通过异步函数和异步栅栏函数进行的任务都是在同一个队列的前提下.

  • 但是在实际开发过程中,我们会进行不断的封装,有时候封装的三方的上层去调用,然而这些接口的底层并不是我们所定义的队列;

  • 那么在不同的队列情况下去执行栅栏函数,将达不到同步的效果. 可自行验证.

  • 因此,栅栏函数的作用常常显得有些鸡肋,使用频率不高.所以这个时候我们引入常用的调度组来解决问题.在引入调度组之前,先探究下信号量.

二、dispatch_semaphore_t

  • 信号量由三个函数来控制实现.

    • dispatch_semaphore_create 信号量创建
    • dispatch_semaphore_wait 信号量等待
    • dispatch_semaphore_signal 信号量释放
  • 信号量处理的特性:具有多元性. 当创建的时候,创建的数值会决定它具有多种效果.
/*!* @函数 dispatch_semaphore_create* @摘要* 使用初始值创建新的计数信号量。* @讨论* 当两个线程需要协调特定事件的完成时,为该值传递零很有用。 * 传递大于零的值对于管理有限的资源池很有用,其中池大小等于该值。* @参数值* 信号量的起始值。 传递小于零的值将导致返回 NULL。* @result* 新创建的信号量,或失败时为 NULL。*/
dispatch_semaphore_t
dispatch_semaphore_create(intptr_t value);
  • 先来一波常规操作: 先waitsignal
    dispatch_queue_t queue   = dispatch_get_global_queue(0, 0);dispatch_semaphore_t sem = dispatch_semaphore_create(1);dispatch_async(queue, ^{dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);sleep(1);NSLog(@"执行任务1");NSLog(@"任务1完成");dispatch_semaphore_signal(sem);});dispatch_async(queue, ^{dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);sleep(1);NSLog(@"执行任务2");NSLog(@"任务2完成");dispatch_semaphore_signal(sem);});dispatch_async(queue, ^{dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);NSLog(@"执行任务3");NSLog(@"任务3完成");dispatch_semaphore_signal(sem);});
  • 执行结果如下:
2021-08-24 11:15:48.497528+0800 006---GCD最大并发数[34214:2687626] 执行任务3
2021-08-24 11:15:48.497718+0800 006---GCD最大并发数[34214:2687626] 任务3完成
2021-08-24 11:15:49.550093+0800 006---GCD最大并发数[34214:2687623] 执行任务1
2021-08-24 11:15:49.550308+0800 006---GCD最大并发数[34214:2687623] 任务1完成
2021-08-24 11:15:50.554766+0800 006---GCD最大并发数[34214:2687630] 执行任务2
2021-08-24 11:15:50.554977+0800 006---GCD最大并发数[34214:2687630] 任务2完成
  • 设置创建时的资源池为1的情况下、任务按照先进先出的顺序执行.
  • 设置创建时的资源池为2的情况下、查看下多个任务的执行结果
    dispatch_queue_t queue   = dispatch_get_global_queue(0, 0);dispatch_semaphore_t sem = dispatch_semaphore_create(2);for (NSInteger i = 1; i < 6; i++) {dispatch_async(queue, ^{dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);NSLog(@"执行任务%ld",(long)i);NSLog(@"任务%ld完成",(long)i);dispatch_semaphore_signal(sem);});}
  • 执行结果如下
2021-08-24 11:28:02.334986+0800 006---GCD最大并发数[34307:2696081] 执行任务1
2021-08-24 11:28:02.334997+0800 006---GCD最大并发数[34307:2696078] 执行任务2
2021-08-24 11:28:02.335154+0800 006---GCD最大并发数[34307:2696081] 任务1完成
2021-08-24 11:28:02.335154+0800 006---GCD最大并发数[34307:2696078] 任务2完成
2021-08-24 11:28:02.335316+0800 006---GCD最大并发数[34307:2696079] 执行任务3
2021-08-24 11:28:02.335323+0800 006---GCD最大并发数[34307:2696077] 执行任务4
2021-08-24 11:28:02.335464+0800 006---GCD最大并发数[34307:2696077] 任务4完成
2021-08-24 11:28:02.335477+0800 006---GCD最大并发数[34307:2696079] 任务3完成
2021-08-24 11:28:02.335834+0800 006---GCD最大并发数[34307:2696083] 执行任务5
2021-08-24 11:28:02.336300+0800 006---GCD最大并发数[34307:2696083] 任务5完成
  • 根据执行任务的结果来看、先执行1、2,然后才有任务1、2的完成结果.这里设置的资源池数量的操作、也就是设置最大并发数.
  • 信号量的本质:就是同步加锁的效果,控制GCD最大并发数

GCD栅栏函数和信号量相关推荐

  1. 多线程基础(七)GCD线程组+栅栏函数

    1.GCD队列组 拦截通知和等待所有任务全部结束在继续往下执行|阻塞 需求:下载两张图片,等两张图片都下载完毕之后,合成图片(这个实例,复习的时候一定要凭空敲出代码练习,好记性不如烂键盘) <两 ...

  2. iOS之深入分析GCD的函数与队列以及多种组合使用

    一.GCD 简介 ① 什么是 GCD ? GCD 是 Apple 开发的一个多核编程的较新的解决方法: GCD 全称:Grand Central Dispatch,是纯 C 语言,提供非常多强大的函数 ...

  3. iOS学习笔记-108.多线程07——CGD栅栏函数、延时、一次性代码

    多线程07CGD栅栏函数延时一次性代码 一说明 1 栅栏函数说明 2 延时执行 3 一次性代码 二栅栏函数 1 说明 2 代码 3 结果 4 结果分析 三延时执行 1 调用NSObject的方法 2 ...

  4. Linux系统编程---17(条件变量及其函数,生产者消费者条件变量模型,生产者与消费者模型(线程安全队列),条件变量优点,信号量及其主要函数,信号量与条件变量的区别,)

    条件变量 条件变量本身不是锁!但它也可以造成线程阻塞.通常与互斥锁配合使用.给多线程提供一个会合的场所. 主要应用函数: pthread_cond_init 函数 pthread_cond_destr ...

  5. iOS 多线程-GCD栅栏方法

    iOS 多线程-GCD任务+队列. iOS 多线程-GCD队列组. iOS 多线程-GCD栅栏方法. 上一篇文章记录了队列组的使用,是为了处理多个任务之间的顺序.但是开发中会出现多组任务的顺序问题. ...

  6. iOS多线程系列之GCD栅栏(barrier)实现同步锁

    多线程编程中很容易出现资源竞争的问题,比如异步读写操作造成数据不同步.那么解决这一问题多线程编程中提供了一种同步机制叫同步锁.iOS中实现同步锁机制的方案不止一种,这里主要介绍一下强大的GCD给出的方 ...

  7. 洛谷P2257 YY的GCD 莫比乌斯函数反演+线性筛

    洛谷P2257 YY的GCD 标签 莫比乌斯反演 线性筛 前言 这题貌似和莫反没多大关系,就是用到了一个莫比乌斯函数的性质了,其他就是推公式,优化和式. 我的第一道懵逼反演-真的好难好难-而且套路特别 ...

  8. [Luogu P2257] YY的GCD (莫比乌斯函数)

    题面 传送门:洛咕 Solution 推到自闭,我好菜啊 显然,这题让我们求: \(\large \sum_{i=1}^{n}\sum_{j=1}^{m}[gcd(i,j)\in prime]\) 根 ...

  9. JAVA 并发编程之三:CountDownLatch(门闩)、CyclicBarrier(栅栏)和Semaphore(信号量) 三种并发策略

    在JDK的并发包中已经提供了几个非常有用的并发工具类.CountDownLatch.CyclicBarrier和Semaphore工具类中提供了一种并发流程控制的手段,Exchanger工具类提供了在 ...

  10. 18、iOS底层分析 - GCD(一)基本概念与简单使用

    GCD 介绍 GCD 全程 Grand Central Dispatch,由C 语言实现,是苹果为多核的并行运行运算提出的解决方案,GCD 会自动利用更多的CPU 内核,自动管理线程的生命周期.GCD ...

最新文章

  1. ASP.NET MVC Framework体验(1):从一个简单实例开始
  2. jenkins+gitlab构建自动化集成
  3. background 旋转_第4章 旋转的圆弧(《Python趣味创意编程》教学视频)
  4. Office2003与Office2007默认打开方式的切换
  5. ubuntu修改主机名
  6. 在flask-sqlalchemy中使用max min avg方法
  7. maven local responsitory 手工新增jar
  8. 如何使用PSDatabaseClone设置基于图像SQL Server数据库配置
  9. kafka内部消费偏移
  10. Lambda表达式只是一颗语法糖?
  11. 数学建模模板(让你也能写论文的模板)
  12. Dell 服务器阵列扩容【经验分享(转)】
  13. 基于端到端深度学习的自动驾驶:AirSim教程(包含Ubuntu18.04下配置AIrsim仿真环境解决方案)
  14. windows在此计算机上找不到系统映象,笔记本电脑没有系统映像怎么办
  15. mac 开启android 模拟器,Mac下安卓模拟器环境配置
  16. WindowsXP系统安装
  17. linux编译jsoncpp
  18. 【数学】均匀分布生成其他分布的方法
  19. conflicting(conflicting)
  20. 趣味题系列:帽子戏法;警察抓逃犯问题 ;史密斯夫妇握手问题

热门文章

  1. Appium_3_环境配置_Appium-desktop配置
  2. 浅谈JavaScript面向对象编程(转自酷勤网)
  3. 苹果马桶iPoo,果粉还hold住吗
  4. 史上最完整的《指环王》魔戒战争大事记(编年体长文)
  5. 盲修瞎练路漫漫,名师点化三日成[转]
  6. 3+1活动:结交一个朋友、参与一项运动 、培养一个兴趣爱好 、阅读一本好书
  7. android手机文件管理器,4 款 Android 文件管理器,总有一款适合你
  8. 九宫格,二十五宫格,甚至八十一宫格 技巧
  9. 跨网段共享服务器文件夹,跨网段文件共享
  10. 开源网站访问统计系统Piwik的基本使用