为何GCD异步并发队列中‘end’未穿插在循环任务中打印?
为什么“end”没有穿插在for循环的异步任务中打印?
嘿,这个问题我之前做iOS并发开发时也踩过类似的坑,咱们来一步步理清楚原因~
核心原因1:GCD队列的FIFO规则
不管是并发队列还是串行队列,GCD的任务队列都遵循**先进先出(FIFO)**的排队逻辑。你看你的代码:
dispatch_queue_t queue = dispatch_queue_create("queue", DISPATCH_QUEUE_CONCURRENT); // 先循环提交1000个打印i的任务 for (NSUInteger i = 0; i < 1000; i++) { dispatch_async(queue, ^{ NSLog(@"i:%lu", (unsigned long)i); }); } // 最后才提交打印end的任务 dispatch_async(queue, ^{ NSLog(@"end:%@", [NSThread currentThread]); });
这1000个i任务会先全部进入队列排队,end任务是最后一个加入队列的。GCD调度任务时,会优先从队列头部取任务执行,只有当前面的1000个任务都被取出队列(开始执行)后,才会轮到end任务。
核心原因2:任务提交的速度远快于执行速度
for循环提交1000个异步任务的速度几乎是瞬间完成的,等你提交end任务时,队列里已经塞满了前面的1000个任务。虽然并发队列可以同时启动多个线程执行任务,但end任务排在队列末尾,大概率要等大部分i任务都执行完(至少是开始执行)后,才会被调度到。
额外小提示:你的代码还有一个变量捕获的问题
顺带提一句,你当前的block捕获i的方式会导致很多打印的i值是重复的(比如最后可能全是999)。因为dispatch_async是异步执行,当block真正跑起来的时候,for循环可能已经执行完了,所有block捕获的都是同一个i的内存地址,最后拿到的是循环结束后的i值。如果想要每个block打印对应的循环值,需要在循环里创建一个局部变量来捕获当前的i:
for (NSUInteger i = 0; i < 1000; i++) { NSUInteger currentI = i; // 用局部变量捕获当前i的值 dispatch_async(queue, ^{ NSLog(@"i:%lu", (unsigned long)currentI); }); }
怎么让“end”穿插打印?
如果想让end能穿插在i的日志里,你需要把end任务的提交时机放到循环中间,比如每隔一定次数就提交一次:
dispatch_queue_t queue = dispatch_queue_create("queue", DISPATCH_QUEUE_CONCURRENT); for (NSUInteger i = 0; i < 1000; i++) { dispatch_async(queue, ^{ NSLog(@"i:%lu", (unsigned long)i); }); // 第500次循环时提交end任务 if (i == 499) { dispatch_async(queue, ^{ NSLog(@"end:%@", [NSThread currentThread]); }); } }
这样end任务会排在第500个i任务之后、剩下的500个i任务之前,GCD调度时就会让end和后面的i任务并发执行,从而出现穿插打印的效果。
内容的提问来源于stack exchange,提问作者young
相关产品推荐
相关产品推荐

