在实际开发中,如果涉及到多线程,十有八九的会用到线程控制,如果是只开启单个线程,在线程结束时候回到主线程刷新UI,这个基操很简单.如果是开启多个线程,线程之间相互依赖,对线程的先后顺序有要求,这个就需要控制线程的生命周期.在之前的一篇文章里详细介绍过dispatch_semaphore_t
这个函数的用法,其实用信号量也完全可以达到控制线程的目的.今天再介绍另外一个函数dispatch_group_t
来实现控制多线程任务,以及如何理解这两个函数和混合使用这两个函数,灵活运用.正文开始,老规矩,先翻译官方注释
第一个函数dispatch_group_t dispatch_group_create(void);
/*!
* @function dispatch_group_create
*
* @abstract
* Creates new group with which blocks may be associated.
* 创建一个新的队列组,这个队列组可能绑定一个 block
*
* @discussion
* This function creates a new group with which blocks may be associated.
* The dispatch group may be used to wait for the completion of the blocks it
* references. The group object memory is freed with dispatch_release().
* 这个函数的功能是创建一个可能绑定 block 的队列组 , 这个队列组在 block 引用完成时启动,在此之前一直处于一种等待的状态,在调用 dispatch_release() 函数时候释放内存
*
* @result
* The newly created group, or NULL on failure.
* 要么创建成功,要么返回 NULL
*/
复制代码
该函数返回的是dispatch_group_t
变量,在 ARC 环境下,不需要再考虑内存问题
第二个函数void dispatch_group_async(dispatch_group_t group,dispatch_queue_t queue,dispatch_block_t block);
/*!
* @function dispatch_group_async
*
* @abstract
* Submits a block to a dispatch queue and associates the block with the given
* dispatch group.
* 给线程提交一个 block, 并将该 block 与给定的队列组绑定
*
* @discussion
* Submits a block to a dispatch queue and associates the block with the given
* dispatch group. The dispatch group may be used to wait for the completion
* of the blocks it references.
* 给线程提交一个 block, 并将该 block 与给定的队列组绑定 , 这个队列组在 block 引用完成时启动,在此之前一直处于一种等待的状态
*
* @param group
* A dispatch group to associate with the submitted block.
* The result of passing NULL in this parameter is undefined.
* 需要绑定的队列组
* @param queue
* The dispatch queue to which the block will be submitted for asynchronous
* invocation.
* 将 block 提交到异步执行的队列
* @param block
* The block to perform asynchronously.
*/
复制代码
该函数返回为空,是一个执行函数
第三个函数void dispatch_group_async_f(dispatch_group_t group, dispatch_queue_t queue, void *_Nullable context, dispatch_function_t work);
这个函数有四个参数,函数名只比上一个函数多了一个后缀,可见其功能是和上一个函数差不多
/*!
* @function dispatch_group_async_f
*
* @abstract
* Submits a function to a dispatch queue and associates the block with the
* given dispatch group.
* 给队列提交一个函数并且给绑定给定队列组的 block
*
* @discussion
* See dispatch_group_async() for details.
* 详情参照 dispatch_group_async() 函数
*
* @param group
* A dispatch group to associate with the submitted function.
* The result of passing NULL in this parameter is undefined.
* 将要绑定功能的队列组,不可传空
*
* @param queue
* The dispatch queue to which the function will be submitted for asynchronous
* invocation.
* 绑定功能的异步执行的队列
*
* @param context
* The application-defined context parameter to pass to the function.
* 功能函数的参数
*
* @param work
* The application-defined function to invoke on the target queue. The first
* parameter passed to this function is the context provided to
* dispatch_group_async_f().
* context 参数通过这个功能函数来调用给当前异步任务
*/
复制代码
按照文档上的描述,它的功能适合上一个一样的,但是参数不同,第一个和第二个不用说,第三个context
是参数,第四个参数是一个指针函数,当执行该指针函数时候会用到第三个传入的参数,具体用法后文会讲到;
第四个函数是等待函数intptr_t dispatch_group_wait(dispatch_group_t group, dispatch_time_t timeout);
/*!
* @function dispatch_group_wait
*
* @abstract
* Wait synchronously until all the blocks associated with a group have
* completed or until the specified timeout has elapsed.
* 阻塞异步任务,当超过设定的时间限制或者队列组完成的时候会停止阻塞
*
* @discussion
* This function waits for the completion of the blocks associated with the
* given dispatch group, and returns after all blocks have completed or when
* the specified timeout has elapsed.
* 这个函数的功能是保证操作之前阻塞线程,当时间过了或者队列组完成的时候就不会再阻塞线程
*
* This function will return immediately if there are no blocks associated
* with the dispatch group (i.e. the group is empty).
* 当没有绑定 block 的时候,也就是说队列组为空的时候,该函数会立即返回
*
* The result of calling this function from multiple threads simultaneously
* with the same dispatch group is undefined.
* 不同线程不可以调用同一个队列组
*
* After the successful return of this function, the dispatch group is empty.
* It may either be released with dispatch_release() or re-used for additional
* blocks. See dispatch_group_async() for more information.
* 当函数成功调用后,该队列组为空,既可以通过释放函数释放它,也可以在此复用
* @param group
* The dispatch group to wait on.
* The result of passing NULL in this parameter is undefined.
* 队列组不可传空
*
* @param timeout
* When to timeout (see dispatch_time). As a convenience, there are the
* DISPATCH_TIME_NOW and DISPATCH_TIME_FOREVER constants.
* 时间参数,目前定义了两个宏 DISPATCH_TIME_NOW 和 DISPATCH_TIME_FOREVER
*
* @result
* Returns zero on success (all blocks associated with the group completed
* within the specified timeout) or non-zero on error (i.e. timed out).
* 返回 0 就表示成功,非 0 就表示错误,即时间超时
*/
复制代码
这个函数和之前信号量的wait
函数功能差不多,都是起到阻塞线程的作用,可用于控制多线程的并发量
第五个函数void dispatch_group_enter(dispatch_group_t group);
/*!
* @function dispatch_group_enter
*
* @abstract
* Manually indicate a block has entered the group
* 手动表示队列组已经进入 block
*
* @discussion
* Calling this function indicates another block has joined the group through
* a means other than dispatch_group_async(). Calls to this function must be
* balanced with dispatch_group_leave().
* 调用此函数表示我是手动加入这个队列组的,而不是通过 dispatch_group_async()函数进入的 ,调用该函数必须搭配 dispatch_group_leave()函数成对使用
*
* @param group
* The dispatch group to update.
* The result of passing NULL in this parameter is undefined.
* 需要更新的队列组
* 不可传空
*/
复制代码
最后一个函数,搭配上一个函数成对使用void dispatch_group_leave(dispatch_group_t group);
/*!
* @function dispatch_group_leave
*
* @abstract
* Manually indicate a block in the group has completed
* 手动声明改队列组已经完成
*
* @discussion
* Calling this function indicates block has completed and left the dispatch
* group by a means other than dispatch_group_async().
* 手动声明这个 block 完成了,不是通过 dispatch_group_async()函数完成的
*
* @param group
* The dispatch group to update.
* The result of passing NULL in this parameter is undefined.
* 需要更新的队列组
* 不可传空
*/
复制代码
用蹩脚的英语将所有函数都翻译一遍,接下来对这几个函数的功能进行简单的总结一下.我们在使用多线程异步执行的时候,什么时候哪个线程完成了,也就是哪个计算完成了,我们得知道,要不然无法完成接下来的操作,而队列组就是提供这个功能的.我们可以等到执行完成之后等待,也可以通过给定的函数自动传达结束的标志,也可通过最后两个函数手动传递结束的标志,这样一来.线程如何依赖,如何交叉,都可以通过这些函数来实现.同时也可以结和信号量来实现相同的功能.
现在有一个需求,同时处理四张表里的数据,四张表分别为 A,B,C,D,其中 A 和 B 是一个小任务,C 和 D是一个小任务,两个小任务结束之后执行任务 E,为优化用户体验,每个小任务完成时候给用户一个toast提示用户,然后根据两个小任务结束之后再做最终的计算.先看第一种写法
- (void)methodFour{
dispatch_queue_t quene = dispatch_get_global_queue(0, 0);
dispatch_group_t group = dispatch_group_create();
dispatch_group_t group2 = dispatch_group_create();
dispatch_group_t group3 = dispatch_group_create();
dispatch_group_t group4 = dispatch_group_create();
dispatch_group_enter(group);
dispatch_async(quene, ^{
for (int i = 0; i < 5; i++) {
NSLog(@"~~~~~~~A");
}
dispatch_group_leave(group);
NSLog(@"A~~~~~~~end");
});
dispatch_group_enter(group);
dispatch_async(quene, ^{
for (int i = 0; i < 5; i++) {
NSLog(@"~~~~~~~B");
}
dispatch_group_leave(group);
NSLog(@"B~~~~~~~end");
});
dispatch_group_enter(group2);
dispatch_async(quene, ^{
for (int i = 0; i < 5; i++) {
NSLog(@"~~~~~~~C");
}
dispatch_group_leave(group2);
NSLog(@"C~~~~~~~end");
});
dispatch_group_enter(group2);
dispatch_async(quene, ^{
for (int i = 0; i < 5; i++) {
NSLog(@"~~~~~~~D");
}
dispatch_group_leave(group2);
NSLog(@"D~~~~~~~end");
});
dispatch_group_enter(group3);
dispatch_group_notify(group, quene, ^{
dispatch_group_leave(group3);
NSLog(@"任务 A + B 结束");
});
dispatch_group_enter(group3);
dispatch_group_notify(group2, quene, ^{
dispatch_group_leave(group3);
NSLog(@"任务 C + D 结束");
});
dispatch_group_enter(group4);
dispatch_group_notify(group3, quene, ^{
dispatch_async(quene, ^{
for (int i = 0; i < 5; i++) {
NSLog(@"~~~~~~~E");
}
NSLog(@"E~~~~~~~end");
dispatch_group_leave(group4);
});
});
dispatch_group_notify(group4, quene, ^{
NSLog(@"任务 E 结束");
});
}
复制代码
看打印结果
仔细观察,发现这玩意儿就跟俄罗斯套娃一样,根据需求一层一层套,我们需要控制哪些线程,就在线程开始之前Enter,等到执行结束,在结束的时候 Leave ,等到都结束的时候就会 notify 你,然后你就可以做你想做的事情了.关于怎么理解,等会儿再讲,接下来结合之前说的信号量,活学活用,两者结合也能实现这个需求
第二种写法
- (void)methodOne{
dispatch_queue_t quene = dispatch_get_global_queue(0, 0);
dispatch_group_t group = dispatch_group_create();
dispatch_group_t group2 = dispatch_group_create();
dispatch_semaphore_t sem = dispatch_semaphore_create(0);
dispatch_group_enter(group);
dispatch_async(quene, ^{
for (int i = 0; i < 3; i++) {
NSLog(@"~~~~~~~A");
}
dispatch_group_leave(group);
NSLog(@"A~~~~~~~end");
});
dispatch_group_enter(group);
dispatch_async(quene, ^{
for (int i = 0; i < 3; i++) {
NSLog(@"~~~~~~~B");
}
dispatch_group_leave(group);
NSLog(@"B~~~~~~~end");
});
dispatch_group_enter(group2);
dispatch_async(quene, ^{
for (int i = 0; i < 3; i++) {
NSLog(@"~~~~~~~C");
}
dispatch_group_leave(group2);
NSLog(@"C~~~~~~~end");
});
dispatch_group_enter(group2);
dispatch_async(quene, ^{
for (int i = 0; i < 3; i++) {
NSLog(@"~~~~~~~D");
}
dispatch_group_leave(group2);
NSLog(@"D~~~~~~~end");
});
dispatch_async(quene, ^{
dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);
dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);
for (int i = 0; i < 3; i++) {
NSLog(@"~~~~~~~E");
}
NSLog(@"E~~~~~~~end");
});
dispatch_group_notify(group, quene, ^{
NSLog(@"任务 A + B 结束");
dispatch_semaphore_signal(sem);
});
dispatch_group_notify(group2, quene, ^{
NSLog(@"任务 C + D 结束");
dispatch_semaphore_signal(sem);
});
}
复制代码
打印结果
这里把最后进入任务 E 的时候用信号量阻塞线程,等到信号量的值为 0 的时候再异步执行任务 E,换汤不换药
第一种方法套过来套过去的,虽然能实现需求,但是很容易把自己套晕,估计苹果的工程师也发现了这个问题,然后就在第一种方法的基础上再做了一次封装,也就是dispatch_group_async
函数,现在介绍第三种方法
- (void)methodTwo{
dispatch_queue_t quene = dispatch_get_global_queue(0, 0);
dispatch_group_t group = dispatch_group_create();
dispatch_group_t group2 = dispatch_group_create();
dispatch_group_t group3 = dispatch_group_create();
dispatch_group_async(group, quene, ^{
for (int i = 0; i < 3; i++) {
NSLog(@"~~~~~~~A~~~%@",[NSThread currentThread]);
}
NSLog(@"~~~~~~~A End~~~%@",[NSThread currentThread]);
});
dispatch_group_async(group, quene, ^{
for (int i = 0; i < 3; i++) {
NSLog(@"~~~~~~~B~~~%@",[NSThread currentThread]);
}
NSLog(@"~~~~~~~B End~~~%@",[NSThread currentThread]);
});
dispatch_group_async(group2, quene, ^{
for (int i = 0; i < 3; i++) {
NSLog(@"~~~~~~~C~~~%@",[NSThread currentThread]);
}
NSLog(@"~~~~~~~C End~~~%@",[NSThread currentThread]);
});
dispatch_group_async(group2, quene, ^{
for (int i = 0; i < 3; i++) {
NSLog(@"~~~~~~~D~~~%@",[NSThread currentThread]);
}
NSLog(@"~~~~~~~D End~~~%@",[NSThread currentThread]);
});
dispatch_group_notify(group, quene, ^{
NSLog(@"任务 A + B 结束~~~~~%@",[NSThread currentThread]);
});
dispatch_group_notify(group2, quene, ^{
NSLog(@"任务 C + D 结束~~~~~%@",[NSThread currentThread]);
});
dispatch_group_async(group3, quene, ^{
dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
dispatch_group_wait(group2, DISPATCH_TIME_FOREVER);
for (int i = 0; i < 3; i++) {
NSLog(@"~~~~~~~E~~~%@",[NSThread currentThread]);
}
NSLog(@"~~~~~~~E End~~~%@",[NSThread currentThread]);
});
dispatch_group_notify(group3, quene, ^{
NSLog(@"任务 E 结束~~~~~%@",[NSThread currentThread]);
});
}
复制代码
在这个方法里,我们用到了dispatch_group_wait
和 dispatch_group_async
这两个函数,根据官方的解释dispatch_group_wait
会阻塞线程,一直到group
完成的时候就会通畅线程.和信号量的作用差不多.
到这里dispatch_group_t
就介绍的的差不多了,所有的函数都用了一遍,举一反三,任务更多的时候也能根据需求来写.接下来讲讲理解部分.
总结:
在很早期写 iOS 的时候,没有接触到这些需求的时候,我就有个想法,对于多线程,有一个比较脑残的想法,就是通过定义一个全局变量来记录多线程是否完成,比如说有三个任务,就全局定义一个 int 类型,每个任务完成就 +1,然后在主线程开个定时器,每 0.1s 执行一次,一直等到 int = 3 的时候再去执行下一步,现在想想那时候的想法真是太脑残了?.然后通过收集资料,观看源码解析,其实dispatch_group_t
是对dispatch_semaphore_t
的一层封装,源码解析这里就不放出来,整体思想是差不多的,系统定义一个变量,这个变量等于 0 的时候线程是畅通的,变量不等于 0 的时候会阻塞线程,变量的加减由开发者控制,从而达到控制线程的目的,所以这也是为什么很多变量的加减操作需要成对出现,和 MRC 一样,你放出来的东西你就要负责回收,系统能给你做的就是在变量等于 0 的时候给你一个通知,而不是像我那样脑残的通过定时器去重复监听;