【iOS】GCD详解

初识GCD

  • 深入理解 Grand Central Dispatch (GCD):iOS 多线程编程的利器
    • 前言
    • 1. GCD 简介
      • 1.1 什么是 GCD?
      • 1.2 为什么使用 GCD?
    • 2. GCD 的核心概念
      • 2.1 任务与队列
      • 2.2 串行队列与并发队列
    • 3. GCD 的 API
      • 3.1 Dispatch Queue
      • 3.2 dispatch_queue_create
      • 3.3 Main Dispatch Queue & Global Dispatch Queue
      • 3.4 dispatch_set_target_queue
      • 3.5 dispatch_after
      • 3.6 Dispatch Group
      • 2.7 GCD 栅栏方法:dispatch_barrier_async
      • 2.8 dispatch_sync 同步线程
      • 2.9 dispatch_apply - GCD 快速迭代
      • 2.10 dispatch_suspend & dispatch_resume
      • 2.11 dispatch_semaphore - GCD 信号量
      • 2.12 dispatch_once

深入理解 Grand Central Dispatch (GCD):iOS 多线程编程的利器

在移动应用开发中,实现良好的性能和响应性对于用户体验至关重要。而 Grand Central Dispatch (GCD) 作为 iOS 平台上的多线程编程技术,为开发者提供了一种简单而强大的方式来实现异步任务的执行,优化应用程序的性能。本篇文章将深入探讨 GCD 的各项功能和用法,帮助读者更好地理解和应用这一强大工具。

前言

在学习 iOS 开发过程中,我们经常会遇到各种关于多线程编程的问题。而对于初学者来说,理解和掌握多线程编程可能是一项具有挑战性的任务。在此之中,Grand Central Dispatch(GCD)作为一种强大的技术,能够帮助开发者简化多线程编程的复杂性,提升应用的性能和响应速度。

1. GCD 简介

1.1 什么是 GCD?

Grand Central Dispatch(GCD)是一种用于实现异步执行任务的技术,主要用于优化应用程序以支持多核处理器和其他对称多处理系统。通过 GCD,开发者只需定义想执行的任务并将其追加到适当的 dispatch Queue 中,GCD 就能自动生成必要的线程并计划执行任务,从而提高了应用的效率。

1.2 为什么使用 GCD?

  • 多核并行运算: GCD 可以利用多核处理器实现并行运算,充分发挥硬件性能。
  • 自动管理线程生命周期: GCD 能够自动管理线程的创建、调度和销毁,减轻了开发者的负担。
  • 简化编程: 开发者只需告诉 GCD 想要执行什么任务,而不需要编写繁琐的线程管理代码。

2. GCD 的核心概念

在学习和应用 GCD 之前,我们需要了解几个核心概念:任务(Task)、队列(Queue)、以及串行队列和并发队列的区别。

2.1 任务与队列

任务是指在线程中执行的操作,通常以 block 的形式存在。而队列则是用来存放任务的等待队列,采用先进先出(FIFO)的原则执行任务。GCD 中有两种方式执行任务:同步执行和异步执行。

  • 同步执行(sync): 在当前线程中执行任务,会等待队列中的任务执行结束后再继续执行。
  • 异步执行(async): 将任务追加到指定的队列中,不会等待任务执行结束,可以继续执行其他任务。

2.2 串行队列与并发队列

  • 串行队列(Serial Dispatch Queue): 每次只有一个任务被执行,任务一个接着一个地执行,不会开启新线程。
  • 并发队列(Concurrent Dispatch Queue): 可以让多个任务并发(同时)执行,可以开启多个线程,提高任务执行效率。

3. GCD 的 API

3.1 Dispatch Queue

Dispatch Queue 是执行任务的等待队列,通过 dispatch_async 函数将任务追加到指定的 Dispatch Queue 中,即可使任务在其他线程执行。

dispatch_async(queue, ^{
    // 想执行的任务
});

Dispatch Queue 包括串行队列和并发队列两种类型,根据需要选择不同的队列类型。

3.2 dispatch_queue_create

dispatch_queue_create 方法用于创建串行队列和并发队列。

// 创建串行队列
dispatch_queue_t serialQueue = dispatch_queue_create("com.example.serialQueue", DISPATCH_QUEUE_SERIAL);

// 创建并发队列
dispatch_queue_t concurrentQueue = dispatch_queue_create("com.example.concurrentQueue", DISPATCH_QUEUE_CONCURRENT);

3.3 Main Dispatch Queue & Global Dispatch Queue

  • 主队列(Main Dispatch Queue): 主队列是一个特殊的串行队列,用于在主线程中执行任务。
  • 全局并发队列(Global Dispatch Queue): GCD 默认提供的并发队列,可通过 dispatch_get_global_queue 方法获取,有四个不同优先级。
// 获取主队列
dispatch_queue_t mainQueue = dispatch_get_main_queue();

// 获取全局并发队列(不同优先级)
dispatch_queue_t highPriorityQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0);

3.4 dispatch_set_target_queue

dispatch_set_target_queue 函数用于变更队列的优先级或执行阶层。

dispatch_set_target_queue(queue1, queue2);

通过设置目标队列,可以实现任务的优先级调整或者阻止任务并发执行。

3.5 dispatch_after

dispatch_after 函数用于延迟一定时间后执行任务。

dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
    // 延迟执行的任务
});

3.6 Dispatch Group

Dispatch Group 是一种用于监视多个任务是否全部执行完毕的机制,常用于等待多个异步任务执行结束后再执行后续操作。

dispatch_group_t group = dispatch_group_create();

dispatch_group_async(group, queue, ^{
    // 异步任务1
});

dispatch_group_async(group, queue, ^{
    // 异步任务2
});

dispatch_group_notify(group, dispatch_get_main_queue(), ^{
    // 所有任务执行完成后的处理
});

2.7 GCD 栅栏方法:dispatch_barrier_async

在访问数据库或文件等资源时,Serial Queue可以有效避免数据竞争。然而,有时我们需要异步执行两组操作,并且第一组操作执行完毕后才能开始执行第二组操作。这时,我们就需要类似于栅栏一样的方法将这两组异步执行的操作分隔开来,确保它们的执行顺序。GCD提供了栅栏方法 dispatch_barrier_async 来解决这个问题。它多用于异步操作。

这个函数通常与创建的并发队列(Concurrent Dispatch Queue)一起使用。下面是一个示例:

- (void)barrier {
    dispatch_queue_t queue = dispatch_queue_create("net.bujige.testQueue", DISPATCH_QUEUE_CONCURRENT);
    
    dispatch_async(queue, ^{
        // 追加任务 1
        [NSThread sleepForTimeInterval:2];              // 模拟耗时操作
        NSLog(@"1---%@",[NSThread currentThread]);      // 打印当前线程
    });
    dispatch_async(queue, ^{
        // 追加任务 2
        [NSThread sleepForTimeInterval:2];              // 模拟耗时操作
        NSLog(@"2---%@",[NSThread currentThread]);      // 打印当前线程
    });
    
    dispatch_barrier_async(queue, ^{
        // 追加任务 barrier
        [NSThread sleepForTimeInterval:2];              // 模拟耗时操作
        NSLog(@"barrier---%@",[NSThread currentThread]);// 打印当前线程
    });
    
    dispatch_async(queue, ^{
        // 追加任务 3
        [NSThread sleepForTimeInterval:2];              // 模拟耗时操作
        NSLog(@"3---%@",[NSThread currentThread]);      // 打印当前线程
    });
    dispatch_async(queue, ^{
        // 追加任务 4
        [NSThread sleepForTimeInterval:2];              // 模拟耗时操作
        NSLog(@"4---%@",[NSThread currentThread]);      // 打印当前线程
    });
}

这段代码会先执行任务1和任务2,然后等待它们执行完毕后再执行栅栏任务,最后执行任务3和任务4。

2.8 dispatch_sync 同步线程

GCD提供了同步执行任务的创建方法 dispatch_sync 和异步执行任务的创建方法 dispatch_async

// 同步执行任务创建方法
dispatch_sync(queue, ^{
    // 这里放同步执行任务代码
});
// 异步执行任务创建方法
dispatch_async(queue, ^{
    // 这里放异步执行任务代码
});

dispatch_async 是非同步的,即将指定的block非同步地追加到队列中,不会进行等待。

dispatch_sync 是同步的,即将指定的block同步地追加到队列中,会等待任务执行完毕后再返回。

然而,使用 dispatch_sync 时需要注意避免死锁。例如:

- (void)syncLockForever {
    dispatch_queue_t queue = dispatch_get_main_queue();
    dispatch_sync(queue,^ {
        NSLog(@"hello");
    });
}

上述代码在主线程中执行,但是同步地向主队列中追加任务,导致了死锁,因为主队列在等待当前任务完成,而当前任务又在等待主队列。这种情况会导致无限等待。

2.9 dispatch_apply - GCD 快速迭代

dispatch_apply 函数可以按照指定的次数将指定的block追加到指定的队列中,并等待全部执行结束。它的作用类似于for循环。

- (void)apply {
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    
    NSLog(@"apply---begin");
    dispatch_apply(6, queue, ^(size_t index) {
        NSLog(@"%zd---%@",index, [NSThread currentThread]);
    });
    NSLog(@"apply---end");
}

以上代码会在全局默认并发队列中执行一个包含6次迭代的任务,打印出每次迭代的索引以及当前线程。

2.10 dispatch_suspend & dispatch_resume

dispatch_suspenddispatch_resume 是两个可以随时挂起和恢复队列的函数。

dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_suspend(queue); // 挂起队列
dispatch_resume(queue);  // 恢复队列

2.11 dispatch_semaphore - GCD 信号量

在实际开发中,dispatch_semaphore 主要用于以下两个方面:

  1. 线程同步: 通过控制信号量来实现多个线程之间的同步,确保某些任务按照预期的顺序执行。
  2. 线程安全: 使用信号量来对共享资源进行加锁,保证多线程环境下的数据安全访问。

在下面的示例中,我们展示了如何使用信号量来实现线程同步:

- (void)semaphoreSync {
    NSLog(@"currentThread---%@", [NSThread currentThread]);  // 打印当前线程
    NSLog(@"semaphore---begin");
    
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
    
    __block int number = 0;
    dispatch_async(queue, ^{
        // 追加任务 1
        [NSThread sleepForTimeInterval:2];  // 模拟耗时操作
        NSLog(@"1---%@", [NSThread currentThread]);  // 打印当前线程
        
        number = 100;
        
        dispatch_semaphore_signal(semaphore); // 发送信号
    });
    
    dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER); // 等待信号
    NSLog(@"semaphore---end, number = %d", number);
}

在上述示例中,我们创建了一个全局并发队列,并在队列中异步执行了一个任务。在任务中,我们模拟了一个耗时操作,并在完成后发送了一个信号。在主线程中,我们通过 dispatch_semaphore_wait 函数等待这个信号,确保任务执行完成后再继续执行后续代码。

2.12 dispatch_once

dispatch_once 函数通常用于创建单例对象,确保该对象在程序生命周期内只被初始化一次。下面是一个简单的示例:

+ (instancetype)sharedInstance {
    static dispatch_once_t onceToken;
    static id sharedInstance = nil;
    dispatch_once(&onceToken, ^{
        sharedInstance = [[self alloc] init];
    });
    return sharedInstance;
}

在上述示例中,我们通过 dispatch_once 函数保证了 sharedInstance 方法中的初始化代码只会被执行一次。这样,无论多少个线程同时调用 sharedInstance 方法,都能够保证只返回同一个单例对象,而不会创建多个实例。

通过合理地使用 dispatch_once 函数,我们能够简洁地实现单例模式,并且保证线程安全性。

你可能感兴趣的:(OC,iOS,ios,objective-c)