您的位置:首页 > 房产 > 建筑 > 并发编程 - GCD队列组

并发编程 - GCD队列组

2024/10/7 10:19:46 来源:https://blog.csdn.net/weixin_39339407/article/details/142213125  浏览:    关键词:并发编程 - GCD队列组

引言

在前面的博客中,我们已经深入探讨了GCD的各种功能,包括创建并发和串行队列、管理异步和同步任务、使用异步和同步栅栏控制操作的顺序,以及利用信号量确保线程安全。今天,我们将介绍GCD的灵一个重要工具——队列组(Queue Groups)。队列组在并发编程中非常实用,特别是在需要同步多个任务并在所有任务完成后执行特定操作时。它在很多并发处理场景中扮演关键角色。接下来,我们将开始深入探讨队列组的使用方法及其在实际应用中的优势。

GCD队列组的概念

GCD队列组(Queue Groups)是一种用于管理和同步多个并发任务的工具。它允许开发者将多个异步任务组,并在所有任务完成后执行一个统一的回调操作。这种机制确保了你可以在复杂的并发场景中精确控制任务的执行顺序。

当我们使用队列组时,可以将若干任务添加到同一个组中,GCD会追踪这些任务的完成情况。你可以选择阻塞当前线程,等待所有任务完成,也可以在所有任务结束后,通过回调通知某个特定的操作。这种灵活性使得队列组成为处理依赖任务、批量网络请求、并发数据处理等场景中的理想选择。

通过队列组,我们能够轻松实现多任务的同步处理,简化代码结构,同时提高代码的可读性和可维护性。

GCD队列组的基本使用

队列组使用起来非常简单一共有三组操作

创建队列组

使用DispatchGroup创建队列组,DispatchGroup只有一个不带参数的初始化方法

let queueGroup = DispatchGroup()

进入和离开队列组

通过调用enter()和leave()方法来管理任务,enter()表示任务进入队列组,leave()表示任务离开队列组。

    //MARK: 队列组 调用enter()和leave()func queueGroup1() {let queueGroup = DispatchGroup()let queue1 = DispatchQueue(label: "com.example.queue1")let queue2 = DispatchQueue(label: "com.example.queue2")let queue3 = DispatchQueue(label: "com.example.queue3")queueGroup.enter()// 异步任务1queue1.async {print("Task 1")sleep(1)queueGroup.leave()}queueGroup.enter()// 异步任务3queue2.async {print("Task 2")sleep(1)queueGroup.leave()}queueGroup.enter()// 异步任务3queue3.async {print("Task 3")sleep(1)queueGroup.leave()}queueGroup.notify(queue: DispatchQueue.main) {print("All tasks are done")}}

每个任务将要执行前,调用进入队列组的方法,执行完成后调用离开队列组的方法,需要保证两个方法的数量配对。

我们可能还会看见下面这种写法:

    func queueGroup() {let queueGroup = DispatchGroup()let queue1 = DispatchQueue(label: "com.example.queue1")let queue2 = DispatchQueue(label: "com.example.queue2")let queue3 = DispatchQueue(label: "com.example.queue3")queue1.async(group: queueGroup) {print("Task 1")sleep(1)}queue2.async(group: queueGroup) {print("Task 2")sleep(1)}queue3.async(group: queueGroup) {print("Task 3")sleep(1)}queueGroup.notify(queue: DispatchQueue.main) {print("All tasks are done")}}

当我们使用queue1.async(group:queueGroup)提交任务时,GCD会自动调用queueGroup.enter(),将该任务添加到队列组中。这个操作表示该任务开始执行,并且需要队列组来跟踪其状态。

当该任务执行完成后,GCD会自动调用queueGroup.leave(),将任务从队列组中移除。这表示任务已经完成,不再需要队列组的跟踪。

等待队列组任务完成

GCD队列组提供了两种方法来处理所有任务完成后的操作。

首先是wait()方法。wait()会阻塞当前线程,直到队列组中的所有任务都完成后,才会继续执行wait()方法后面的代码。这种方式适用于那些必须在所有并发任务完成后才继续执行的场景。

然而,由于它会阻塞当前线程,因此在主线程中使用时需要特别小心,避免影响应用的响应性。

    //MARK: 队列组 等待所有任务完成func queueGroupWait() {let queueGroup = DispatchGroup()let queue1 = DispatchQueue(label: "com.example.queue1")let queue2 = DispatchQueue(label: "com.example.queue2")let queue3 = DispatchQueue(label: "com.example.queue3")queue1.async(group: queueGroup) {print("Task 1")sleep(1)}queue2.async(group: queueGroup) {print("Task 2")sleep(1)}queue3.async(group: queueGroup) {print("Task 3")sleep(1)}// 等待所有任务完成 - 阻塞当前线程queueGroup.wait()print("All tasks are done")}

其次是notify()方法。与wait()方法不同,notify()并不会阻塞当前线程。相反,它允许我们指定一个代码块,这个代码块会在队列组中的所有任务完成后异步执行。我们可以选择在指定的队列上执行这些操作,比如在主队列上更新UI。notify()方法更加灵活,适用于大多数并发编程场景。

    //MARK: 队列组 通知func queueGroupNotify() {let queueGroup = DispatchGroup()let queue1 = DispatchQueue(label: "com.example.queue1")let queue2 = DispatchQueue(label: "com.example.queue2")let queue3 = DispatchQueue(label: "com.example.queue3")queue1.async(group: queueGroup) {print("Task 1")sleep(1)}queue2.async(group: queueGroup) {print("Task 2")sleep(1)}queue3.async(group: queueGroup) {print("Task 3")sleep(1)}queueGroup.notify(queue: DispatchQueue.main) {print("All tasks are done")}}

GCD队列组在实际项目中的使用

在实际开发中队列组的使用非常普遍,其中一个比较典型的实际应用场景是发布动态中的媒体资源上传。

在开发社交媒体应用时,用户发布动态通常会附带图片、视频等多媒体资源。为了确保所有媒体资源上传成功后再调用发布接口,这时候就可以使用GCD队列组来同步这些上传任务,并在所有任务完成后调用发布接口。

代码如下:

    /// 队列组private var group = DispatchGroup()/// 资源数组private let mediaResources = ["image1.jpg", "video1.mp4", "image2.jpg"]//MARK: 上传private func startUpload() {for resource in mediaResources {uploadMediaResource(resource)}group.notify(queue: DispatchQueue.main) {print("All media resources have been uploaded")self.startPublish()}}//MARK: 发布private func startPublish() {print("Start publishing...")}//MARK: 模拟上传任务private func uploadMediaResource(_ resource: String) {group.enter() // 进组,表示开始一个任务DispatchQueue.global().async {print("Uploading \(resource)...")sleep(2) // 模拟上传时间let success = Bool.random() // 随机模拟成功或失败if success {print("\(resource) upload succeeded")} else {print("Error: \(resource) failed to upload")}self.group.leave() // 出组,表示任务完成}}
  • 任务进组和出组:每个资源上传任务开始时,调用enter()手动进组,表示该任务已加入队列组。任务完成后,调用leavel()手动出组。
  • 使用notify()方法:notify()方法不会阻塞当前线程,而是允许你指定一个代码块,当队列组中的所有任务完成后,异步执行这个代码块。在这个例子中,notify被用来在主线程上调用发布接口,确保所有资源都已上传完成。

GCD队列组的注意事项

在使用GCD队列组时,尽管它为多任务处理提供了强大的同步机制,但仍然有一些关键点需要注意,以避免常见的错误和潜在的问题:

  1. 避免阻塞主线程:虽然wait()方法可以方便地等待所有任务完成,但它会阻塞当前线程。在主线程中使用时,可以会导致UI卡顿或应用无响应。因此,通常推荐在主线程中使用notify(),而非wait()。
  2. 手动管理进组和出组:使用enter()和leave()方法时,要确保调用的对称性。每次enter()必须对应一个leave(),否则可能会导致队列组永远无法退出,进而造成死锁或资源泄漏,这在负责的逻辑中尤为重要,我们需要也别小心。
  3. 合理管理任务的执行顺序:在某些场景下,队列组中的任务顺序可能并不重要,但在需要保证顺序是,要明确使用串行队列或在任务中嵌套其他同步机制。队列组本身并不保证任务的执行顺序,尤其是在并发队列中使用时。
  4. 正确选择队列类型:在使用notify()方法时,注意选择合适的队列。如果需要在后台处理某些任务,应将notify()放在合适的全局或自定义队列上。如果需要更新UI,notify()应该放在主队列上,以避免线程不安全操作。
  5. 错误处理:在队列组中的每个任务中,都应该包含合理的错误处理机制。队列组本身不处理任务的成功或失败,因此需要在任务完成后进行检查,并根据需要采取相应措施。

结语

通过本文的介绍,我们深入探讨了GCD队列组这一强大工具在并发编程中的应用。队列组不仅能够帮助我们同步多任务,还能在所有任务完成后执行特定的收尾操作,这在实际开发中非常场景且使用。无论是上传多个媒体资源并在完成后统一发布,还是在处理并发任务时确保所有步骤顺序执行,队列组都能提供简洁而有效的解决方案。

但在使用队列组时,务必要注意线程安全,任务进出组的对称性,以及合理选择notify()或wait()方法,以避免潜在的问题。通过正确地掌握和应用队列组,我们可以更好地管理多任务处理,提升代码的可靠性和可维护性。

希望本文能够帮助大家更好地理解并利用GCD队列组,为开发工作增添一份便利和效率。如果大家在并发编程中有更多的需求或疑问,不妨继续探索GCD提供的其他强大工具,相信你会在其中发现更多可能。

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com