iOS 之 dispatch_group_t 控制多任务及结合信号量(dispatch_semaphore_t) 完成线程任务同步

在实际开发中,如果涉及到多线程,十有八九的会用到线程控制,如果是只开启单个线程,在线程结束时候回到主线程刷新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 结束");
    });

}

复制代码

看打印结果

截屏2021-06-18 下午9.31.04.png

仔细观察,发现这玩意儿就跟俄罗斯套娃一样,根据需求一层一层套,我们需要控制哪些线程,就在线程开始之前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);
    });

}

复制代码

打印结果

截屏2021-06-18 下午9.38.52.png

这里把最后进入任务 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]);
    });
}

复制代码

截屏2021-06-18 下午10.19.55.png

在这个方法里,我们用到了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 的时候给你一个通知,而不是像我那样脑残的通过定时器去重复监听;

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享