iOS面试:4.多线程GCD

时间:2024-02-24 10:08:02

一、多线程基础知识

1.1 什么是进程?

进程是指在系统中正在运行的一个应用程序。对于电脑而已,你打开一个软件,就相当于开启了一个进程。对于手机而已,你打开了一个APP,就相当于开启了一个进程。

1.2 什么是线程?

线程是进程的基本执行单位。一个进程中至少会有一条线程,当然也可能会有多条线程。比如你使用QQ音乐听歌,系统会创建一条线程去播放音乐。使用QQ音乐下载歌曲,系统会创建一条线程去下载歌曲。这两个操作是可以同时进行的,也就说一个进程中可以同时运行多条线程。

1.3 任务执行的方式

任务执行的方式分为两种:串行和并行,也可以叫做同步和异步。串行指多个任务一个一个排队的执行,并行指多个任务并列执行。

1.4 多线程是什么?

多线程就是一个进程中可以开启多条线程,每条线程可以并行(同时)执行不同的任务。

1.5多线程可以干什么?

多线程可以同时执行不同的任务,提高系统的运行效率。

1.6 多线程的优点

(1) 多线程可以同时执行不同的任务,提高系统的运行效率。
(2) 在app开发中使用多线程,可以减少页面卡顿,提升流畅度。

1.7 多线程的缺点

(1) 增大CPU调度开销:线程越多,CPU在调度线程上的开销就越大。
(2) 增加程序的复杂性:比如线程之间的通信、多线程的数据共享。

1.8 iOS有四种多线程编程技术,分别是:

(1) pThread
(2) NSThread
(3) Cocoa NSOperation
(4) CGD (全称:Grand Central Dispathch)

这几种编程方式从上到下,抽象度层次是从低到高的,抽象度越高的使用越简单,也是Apple最推荐使用的。

二、GCD

2.1 GCD简介

在 iOS 开发中,GCD(Grand Central Dispatch)是一种用于实现并发编程的技术,它提供了一套强大的 API,帮助开发者管理并发任务、线程池、任务调度等。GCD 的主要特点和优势包括:

  1. 简单易用:GCD 提供了一套简单而强大的 API,可以帮助开发者轻松实现多线程编程和异步操作,而不需要手动管理线程的创建和销毁。

  2. 高效性能:GCD 可以利用系统资源高效地管理并发任务,根据系统负载和任务优先级动态调度任务的执行,提高应用程序的性能和响应速度。

  3. 多线程支持:GCD 支持串行队列、并发队列、主队列等不同类型的队列,可以满足不同场景下的多线程需求,实现任务的并发执行和按顺序执行。

  4. 线程安全:GCD 自动处理了线程同步和锁的问题,可以避免多线程编程中常见的竞争条件和数据访问冲突,提高程序的稳定性。

  5. 异步操作:GCD 支持异步操作,可以在后台执行耗时的任务,避免阻塞主线程,保持应用的流畅性和响应性。

在 iOS 开发中,开发者可以使用 GCD 来实现各种并发任务,例如网络请求、数据处理、图片加载等。常用的 GCD API 包括:

  • dispatch_async:将任务提交到队列中异步执行。
  • dispatch_sync:将任务提交到队列中同步执行。
  • dispatch_group:用于管理一组任务的执行。
  • dispatch_barrier_async:在并发队列中插入一个 barrier,保证前面的任务在 barrier 之前执行,后面的任务在 barrier 之后执行。

总的来说,GCD 是 iOS 开发中非常重要的并发编程工具,可以帮助开发者实现多线程编程、异步操作和任务调度,提高应用程序的性能和用户体验。熟练掌握 GCD 可以让开发者更好地处理并发编程中的各种问题,提高代码的质量和可维护性。

GCD是苹果提出的异步执行任务的技术,用简单的方法实现了多线程编程。GCD用的也是C语言,里面引入了OC的block语法,使用起来更加的灵活和方便。

二、GCD的基本使用

2.1 如何创建一个线程

CGD创建一个线程有两种方式,一种是利用全局的dispatch_get_global_queue来创建,另外一种是自定义创建线程,即可以自己给线程取名字的方式来创建线程。

2.1.1 利用全局的dispatch_get_global_queue来创建线程
 dispatch_async(dispatch_get_global_queue(0, 0), ^{
        NSLog(@"start task 1");
        //执行耗时任务
        [NSThread sleepForTimeInterval:3];
        dispatch_async(dispatch_get_main_queue(), ^{
           //回到主线程刷新UI
            NSLog(@"回到主线程刷新UI");
        });
  });
2.1.2 自定义创建线程

这种推荐使用应用程序ID这种逆序全程域名,如果嫌命名麻烦设置为NULL也可以。给Dispatch Queue署名,是为了在程序崩溃的时候,能够更快速的找到出错的线程。

dispatch_queue_t queue = dispatch_queue_create("com.test.gcd.queue", NULL);

dispatch_async(queue, ^{
        NSLog(@"start task 1");
        [NSThread sleepForTimeInterval:3];
        NSLog(@"end task 1");
});
2.2 线程的设置

创建了一个线程之后,可以对线程进行设置。可以设置的内容包括:设置线程的执行方式和线程的执行顺序。

dispatch_get_global_queue(0, 0)有两个参数,第一个参数是设置线程的优先级,第二个参数是设置线程的执行方式。

2.2.1 设置线程执行方式

线程的执行方式有两种:串行和并行,即同步和异步。

Dispatch Queue的种类:Serial Dispatch Queue(串行) 和 Concurrent Dispatch Queue(并行)。

//线程执行方式:串行,也可填0,0就代表Serial Dispatch Queue
dispatch_async(dispatch_get_global_queue(0, Serial Dispatch Queue), ^{
        NSLog(@"start task 1");
        //执行耗时任务
        [NSThread sleepForTimeInterval:3];
});
//线程执行方式:串行,也可填NULL,NULL就代表Serial Dispatch Queue
dispatch_queue_t queue = dispatch_queue_create("com.test.gcd.queue", Serial Dispatch Queue);

dispatch_async(queue, ^{
        NSLog(@"start task 1");
        [NSThread sleepForTimeInterval:3];
        NSLog(@"end task 1");
});
2.2.2 设置线程执行顺序

线程的执行顺序有以下几种:

  • 默认:DISPATCH_QUEUE_PRIORITY_DEFAULT
  • 高:DISPATCH_QUEUE_PRIORITY_HIGH
  • 低:DISPATCH_QUEUE_PRIORITY_LOW
  • 后台:DISPATCH_QUEUE_PRIORITY_BACKGROUND
//在默认优先级的 Global Dispatch Queue 中执行Block
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
      //可并行执行的处理
      //在Main Dispatch Queue中执行Block
      dispatch_async(dispatch_get_main_queue(), ^{
      //只能在主线程中执行的处理
    });
});

对于自定义创建的线程,需要用dispatch_set_target_queue设置线程优先级:

dispatch_queue_t queue = dispatch_queue_create("com.example.gcd.MySerialDispatchQueue", NULL);
dispatch_set_target_queue(queue, DISPATCH_QUEUE_PRIORITY_HIGH);

三、GCD队列与函数

3.1 GCD 串行队列与并发队列

在 GCD(Grand Central Dispatch)中,队列是用来管理任务的执行顺序的一种机制。GCD 中的队列分为两种类型:串行队列(Serial Queue)和并发队列(Concurrent Queue)。

  1. 串行队列(Serial Queue):
    串行队列中的任务按照先进先出(FIFO)的顺序依次执行,每次只执行一个任务。一个任务执行完成后,才会执行下一个任务。串行队列可以确保任务按照特定的顺序执行,适用于需要顺序执行任务的场景。

  2. 并发队列(Concurrent Queue):
    并发队列中的任务可以同时执行,可以并发地执行多个任务。并发队列中的任务的执行顺序是不确定的,可能同时执行多个任务,也可能顺序执行。并发队列适用于需要同时执行多个任务且任务之间相互独立的场景。

在 GCD 中,有以下几种类型的队列:

  • 主队列(Main Queue):串行队列,用于在主线程上执行任务,通常用于更新 UI。
  • 全局并发队列(Global Concurrent Queue):系统提供的并发队列,有多个优先级(QoS)可供选择。
  • 自定义队列(Custom Queue):通过 dispatch_queue_create 函数创建的自定义队列,可以设置为串行队列或并发队列。
//主队列,相当于主线程的queue,有且仅有一个。
dispatch_get_main_queue

// 全局并发队列,相当于全局的queue,可以有多个。
dispatch_get_global_queue

//自定义队列, 普通串行队列
dispatch_queue_t serial = dispatch_queue_create("com.test.Serial", DISPATCH_QUEUE_SERIAL); 普通串行队列

//自定义队列,普通并发队列
dispatch_queue_t concurrent = dispatch_queue_create("comd.test.concurrent", DISPATCH_QUEUE_CONCURRENT);  
3.2 GCD 同步函数与异步函数

在 GCD(Grand Central Dispatch)中,dispatch_asyncdispatch_sync 是两个常用的函数,用于将任务提交到队列中异步或同步执行。

3.2.1 dispatch_async

dispatch_async 函数用于异步地将任务提交到指定的队列中执行。该函数会立即返回,不会等待任务执行完成,而是在后台进行执行。适用于不需要等待任务执行完成就可以继续执行后续代码的场景。

使用方法如下:

dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(queue, ^{
    // 任务代码块
    NSLog(@"Task executed asynchronously");
});
3.2.2 dispatch_sync

dispatch_sync 函数用于同步地将任务提交到指定的队列中执行。该函数会阻塞当前线程,直到任务执行完成才会继续执行后续代码。适用于需要等待任务执行完成后再继续执行后续代码的场景。

使用方法如下:

dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_sync(queue, ^{
    // 任务代码块
    NSLog(@"Task executed synchronously");
});

区别和用法总结:

  • dispatch_async 是异步执行任务,不会阻塞当前线程,适用于并发执行任务的场景。
  • dispatch_sync 是同步执行任务,会阻塞当前线程,等待任务执行完成后才会继续执行后续代码。
  • 通常情况下,推荐使用 dispatch_async 来避免阻塞主线程,提高程序的响应速度。
  • 在需要确保任务按照特定顺序执行或需要等待任务执行完成后再继续执行后续代码的情况下,可以使用 dispatch_sync

总之,dispatch_asyncdispatch_sync 是 GCD 中用于提交任务到队列中异步或同步执行的两个重要函数,根据具体的需求选择合适的函数来提交任务,可以更好地控制任务的执行方式。

四、函数与队列的组合

在上一小节中,我们知道GCD中函数有两种 同步和异步,队列分为两种 串行和并发,加上两种特殊的队列 主队列和全局并发队列,所以可以有如下几种组合:

  1. 同步函数 + 主队列
  2. 同步函数 + 全局并发队列
  3. 同步函数 + 普通串行队列
  4. 同步函数 + 普通并发队列
  5. 异步函数 + 主队列
  6. 异步函数 + 全局并发队列
  7. 异步函数 + 普通串行队列
  8. 异步函数 + 普通并发队列

其实主队列是一种特殊的串行队列,全局并发队列是一种特殊的并发队列,不过在某些情况下与普通队列有所不同,我们分别通过demo来看下这几种组合会有什么效果。

4.1 同步函数 + 主队列

同步函数 + 主队列的代码如下:

- (void)syncMain {

    dispatch_queue_t mainQueue = dispatch_get_main_queue();

    NSLog(@"1 ---- %@",[NSThread currentThread]);

    dispatch_sync(mainQueue, ^{

        NSLog(@"2 ---- %@",[NSThread currentThread]);

    });

    NSLog(@"3 ---- %@",[NSThread currentThread]);
}

代码执行结果如下:

在这里插入图片描述

可以看到同步函数 + 主队列的执行结果是发生了死锁,_dispatch_sync_f_slow () 是发生死锁时GCD调用的函数。发生死锁的原因如下:

在这里插入图片描述

4.2 异步函数 + 主队列

异步函数 + 主队列的代码如下:

- (void)asyncMain {
    dispatch_queue_t mainQueue = dispatch_get_main_queue();
    NSLog(@"1 ---- %@",[NSThread currentThread]);
    dispatch_async(mainQueue, ^{
        NSLog(@"2 ---- %@",[NSThread currentThread]);
    });
    NSLog(@"3 ---- %@",[NSThread currentThread]);
}

代码执行结果如下:

在这里插入图片描述

从执行结果可以发现,使用异步函数没有阻塞后面的任务,因此也不会发生死锁。并且可以发现,在主队列下使用异步函数也不会开启新的线程。

4.3 同步函数 + 全局并发队列

同步函数 + 全局并发队列的代码如下:

- (void)syncGlobal {
    dispatch_queue_t global = dispatch_get_global_queue(0, 0);
    NSLog(@"1 ---- %@",[NSThread currentThread]);
    dispatch_sync(global, ^{
        NSLog(@"2 ---- %@",[NSThread currentThread]);
        dispatch_sync(global, ^{
            NSLog(@"3 ---- %@",[NSThread currentThread]);
        });
        NSLog(@"4 ---- %@",[NSThread currentThread]);
    });
    NSLog(@"5 ---- %@",[NSThread currentThread]);
}

这次代码稍微复杂一些,内部再嵌套了一个sync函数,其执行结果如下:

在这里插入图片描述

通过结果可以发现,在全局并发队列下,使用sync不会死锁。并且同步函数不会开启新的线程,因此虽然是在全局并发队列中,但是依然是在主线程。

4.4 异步函数 + 全局并发队列

同步函数 + 全局并发队列的代码如下:

- (void)syncGlobal {
    dispatch_queue_t global = dispatch_get_global_queue(0, 0);
    NSLog(@"1 ---- %@",[NSThread currentThread]);
    dispatch_async(global, ^{
        NSLog(@"2 ---- %@",[NSThread currentThread]);
        dispatch_async(global, ^{
            NSLog(@"3 ---- %@",[NSThread currentThread]);
        });
        NSLog(@"4 ---- %@",[NSThread currentThread]);
    });
    NSLog(@"5 ---- %@",[NSThread currentThread]);
}

代码执行结果如下:

在这里插入图片描述

可以发现代码是异步执行,不会阻塞,也不会死锁,并且async函数会开启新的线程6和7。

4.5 同步函数 + 普通串行队列

同步函数 + 普通串行队列的代码,我们分两部分看,先看第一部分如下:

- (void)syncSerial {
    dispatch_queue_t serial = dispatch_queue_create("BPTest.sync.Serial", DISPATCH_QUEUE_SERIAL);
    NSLog(@"1 ---- %@",[NSThread currentThread]);
    dispatch_sync(serial, ^{
        NSLog(@"2 ---- %@",[NSThread currentThread]);
//        dispatch_sync(serial, ^{
//            NSLog(@"3 ---- %@",[NSThread currentThread]);
//        });
//        NSLog(@"4 ---- %@",[NSThread currentThread]);
    });
    NSLog(@"3 ---- %@",[NSThread currentThread]);
}

执行结果如下:

在这里插入图片描述

可以发现此处并未发生死锁,对比同步函数 + 主队列,同样都是串行队列,为何主队列会死锁闪退,而普通的串行队列可以正常运行呢?其原因如下图:

在这里插入图片描述

同步函数 + 主队列中,因为只有一个主队列,所以会发生死锁,而在同步函数 + 串行队列中,因为有除了有一个串行队列外,还有个默认的主队列,我们的次任务是添加到串行队列中的,因此不会死锁闪退。
那么同步函数 + 串行队列一定是安全的吗?我们接着看下面的代码:

在这里插入图片描述

可以发现,同步函数 + 串行队列一样会发生闪退,那么我们分析下这次死锁闪退的原因,如下图所示:

在这里插入图片描述

其实原因与主队列闪退是一致的,本次所添加的任务都是添加到我们创建的串行队列中,所以会发生和主队列一样死锁闪退。

4.6 异步函数 + 普通串行队列

异步函数 + 普通串行队列的代码如下:

- (void)asyncSerial {
    dispatch_queue_t serial = dispatch_queue_create("BPTest.async.Serial", DISPATCH_QUEUE_SERIAL);
    NSLog(@"1 ---- %@",[NSThread currentThread]);
    dispatch_async(serial, ^{
        NSLog(@"2 ---- %@",[NSThread currentThread]);
        dispatch_async(serial, ^{
            NSLog(@"3 ---- %@",[NSThread currentThread]);
        });
        NSLog(@"4 ---- %@",[NSThread currentThread]);
    });
    NSLog(@"5 ---- %@",[NSThread currentThread]);
}

代码执行结果如下:

在这里插入图片描述

虽然还是添加在串行队列中,但是因为使用的是异步函数,不会发生阻塞,所以也不会产生死锁。

4.7 同步函数 + 普通并发队列

同步函数 + 普通并发队列的代码如下:

- (void)syncConcurrent {
    dispatch_queue_t concurrent = dispatch_queue_create("BPTest.sync.concurrent", DISPATCH_QUEUE_CONCURRENT);
    NSLog(@"1 ---- %@",[NSThread currentThread]);
    dispatch_sync(concurrent, ^{
        NSLog(@"2 ---- %@",[NSThread currentThread]);
        dispatch_sync(concurrent, ^{
            NSLog(@"3 ---- %@",[NSThread currentThread]);
        });
        NSLog(@"4 ---- %@",[NSThread currentThread]);
    });
    NSLog(@"5 ---- %@",[NSThread currentThread]);
}

执行结果如下:

在这里插入图片描述

与同步函数 + 全局并发队列的情况一致,也不会发生死锁闪退,这里分析下为何使用并发队列没有闪退,而用串行队列闪退了,分析如图:

在这里插入图片描述

4.8 异步函数 + 普通并发队列

代码及执行结果如下:

在这里插入图片描述

这里既不会阻塞,也不会死锁。

4.9 总结

本篇主要介绍了GCD的队列和函数,可以得到以下几个结论:

  1. 函数分为同步函数和异步函数,函数控制是否有开辟线程的能力,同步函数不具有开启新线程的能力,异步函数具有开辟新线程的能力,但是会根据实际情况确定是否开辟新线程
  2. 队列主要分为串行队列和并发队列,队列决定了线程的调度能力,串行队列只能调度一个线程,因此任务只能顺序执行,并发队列则具有并发调度的能力。
    队列和函数的组合有以下几个结果:
队列 同步 异步
主队列 死锁闪退 正常,但不会开辟新线程(主队列中只有主线程)
全局并发队列 正常,同步不开辟新线程 正常,开辟新线程
普通串行队列 部分情况下会死锁闪退 正常,会开辟新线程
普通并发列 正常,同步函数不开启新线程 正常,会开辟新线程

五、GCD线程组

在 GCD(Grand Central Dispatch)中,线程组(Dispatch Group)是一种用于管理多个任务的机制,它可以帮助我们在多个任务执行完成后执行特定的代码块。线程组可以让我们将一组任务关联在一起,然后等待这些任务全部完成后再执行其他操作。

下面是线程组的一些重要概念和使用方法:

  1. 创建线程组:

    • 使用 dispatch_group_create 函数来创建一个线程组对象。
  2. 将任务添加到线程组中:

    • 使用 dispatch_group_async 函数将任务异步提交到指定的队列中,并将该任务与线程组关联起来。
  3. 等待线程组中的任务执行完成:

    • 使用 dispatch_group_wait 函数可以等待线程组中的所有任务执行完成,该函数会阻塞当前线程直到所有任务执行完成。
    • 使用 dispatch_group_notify 函数可以在线程组中的所有任务执行完成后执行特定的代码块,不会阻塞当前线程。
5.1 dispatch_group_wait 函数

在 Objective-C 中,GCD(Grand Central Dispatch)提供了 dispatch_group_wait 函数,用于等待指定的 dispatch group 中的所有任务执行完成。dispatch_group_wait 函数会阻塞当前线程,直到指定的 dispatch group 中的所有任务都执行完成或超时。

使用 dispatch_group_wait 函数的步骤如下:

  1. 创建一个 dispatch group,并将需要等待的任务添加到该 group 中。
  2. 使用 dispatch_group_wait 函数等待指定的 dispatch group 中的任务执行完成。
  3. 任务执行完成后,继续执行后续代码。

下面是一个示例代码,演示了如何使用 dispatch_group_wait 函数等待指定的 dispatch group 中的任务执行完成:

dispatch_group_t group = dispatch_group_create();
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

// 添加任务到 dispatch group
dispatch_group_async(group, queue, ^{
    // 任务1
    NSLog(@"Task 1 executed");
});

dispatch_group_async(group, queue, ^{
    // 任务2
    NSLog(@"Task 2 executed");
});

// 等待 dispatch group 中的任务执行完成
dispatch_group_wait(group, DISPATCH_TIME_FOREVER);

// 所有任务执行完成后,继续执行后续代码
NSLog(@"All tasks executed");

在上面的示例中,首先创建了一个 dispatch group,并向该 group 中添加了两个任务。然后使用 dispatch_group_wait 函数等待 dispatch group 中的任务执行完成。一旦所有任务执行完成,dispatch_group_wait 函数返回,继续执行后续代码。

需要注意的是,dispatch_group_wait 函数会阻塞当前线程,直到所有任务执行完成或超时。因此,在使用该函数时,需要确保不会导致死锁或线程阻塞的情况发生。

总之,dispatch_group_wait 函数是 GCD 中用于等待指定的 dispatch group 中的任务执行完成的函数,可以帮助控制任务的执行顺序和同步。

5.2 dispatch_group_notify 函数

在 Objective-C 中,GCD(Grand Central Dispatch)提供了 dispatch_group_notify 函数,用于在指定的 dispatch group 中的所有任务执行完成后执行一个回调块。这个函数通常与 dispatch_group_enterdispatch_group_leave 配合使用,用于异步执行一组任务并在所有任务执行完成后执行额外的操作。

以下是 dispatch_group_notify 函数的介绍和使用方法:

  1. dispatch_group_notify
    dispatch_group_notify 函数用于设置一个回调块,在指定的 dispatch group 中的所有任务执行完成后执行。当 dispatch group 中的任务计数为零时,即所有任务执行完成时,指定的回调块会被异步执行。

使用 dispatch_group_notify 函数的步骤如下:

  1. 创建一个 dispatch group。
  2. 在需要执行的任务开始前调用 dispatch_group_enter
  3. 在任务执行完成后调用 dispatch_group_leave
  4. 使用 dispatch_group_notify 函数设置一个回调块,在所有任务执行完成后执行额外的操作。

下面是一个示例代码,演示了如何使用 dispatch_group_notify 函数在所有任务执行完成后执行额外的操作:

dispatch_group_t group = dispatch_group_create();
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

dispatch_group_enter(group);
dispatch_async(queue, ^{
    // 任务1
    NSLog(@"Task 1 executed");
    dispatch_group_leave(group);
});

dispatch_group_enter(group);
dispatch_async(queue, ^{
    // 任务2
    NSLog(@"Task 2 executed");
    dispatch_group_leave(group);
});

// 设置一个回调块,在所有任务执行完成后执行额外的操作
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
    NSLog(@"All tasks executed");
});

// 等待所有任务执行完成
dispatch_group_wait(group, DISPATCH_TIME_FOREVER);

在上面的示例中,首先创建了一个 dispatch group,并向该 group 中添加了两个任务。在每个任务开始前调用 dispatch_group_enter,在任务执行完成后调用 dispatch_group_leave。最后使用 dispatch_group_notify 函数设置一个回调块,在所有任务执行完成后打印信息。

通过使用 dispatch_group_notify 函数,可以在所有任务执行完成后执行额外的操作,实现对一组任务的控制和同步。

5.3 dispatch_group_enter dispatch_group_leave 函数

在 Objective-C 中,GCD(Grand Central Dispatch)提供了 dispatch_group_enterdispatch_group_leave 函数,用于管理 dispatch group 中的任务计数。这两个函数通常与 dispatch_group_notify 配合使用,用于异步执行一组任务并在所有任务执行完成后执行额外的操作。

以下是 dispatch_group_enterdispatch_group_leave 函数的介绍和使用方法:

  1. dispatch_group_enter
    dispatch_group_enter 函数用于向指定的 dispatch group 中添加一个任务,增加该 group 的任务计数。在任务开始执行前调用 dispatch_group_enter,表示有一个任务要执行。

  2. dispatch_group_leave
    dispatch_group_leave 函数用于标记指定的 dispatch group 中的一个任务已经执行完成,减少该 group 的任务计数。在任务执行完成后调用 dispatch_group_leave,表示一个任务执行完成。

使用 dispatch_group_enterdispatch_group_leave 函数的步骤如下:

  1. 创建一个 dispatch group。
  2. 在需要执行的任务开始前调用 dispatch_group_enter
  3. 在任务执行完成后调用 dispatch_group_leave
  4. 使用 dispatch_group_notify 函数设置一个回调块,在所有任务执行完成后执行额外的操作。

下面是一个示例代码,演示了如何使用 dispatch_group_enterdispatch_group_leave 函数管理 dispatch group 中的任务计数:

dispatch_group_t group = dispatch_group_create();
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

dispatch_group_enter(group);
dispatch_async(queue, ^{
    // 任务1
    NSLog(@"Task 1 executed");
    dispatch_group_leave(group);
});

dispatch_group_enter(group);
dispatch_async(queue, ^{
    // 任务2
    NSLog(@"Task 2 executed");
    dispatch_group_leave(group);
});

// 设置一个回调块,在所有任务执行完成后执行额外的操作
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
    NSLog(@"All tasks executed");
});

// 等待所有任务执行完成
dispatch_group_wait(group, DISPATCH_TIME_FOREVER);

在上面的示例中,首先创建了一个 dispatch group,并向该 group 中添加了两个任务。在每个任务开始前调用 dispatch_group_enter,在任务执行完成后调用 dispatch_group_leave。最后使用 dispatch_group_notify 函数设置一个回调块,在所有任务执行完成后打印信息。

通过使用 dispatch_group_enterdispatch_group_leave 函数,可以更灵活地管理 dispatch group 中的任务计数,实现对一组任务的控制和同步。

六、GCD其他用法

6.1 dispatch_once 只执行一次方法

dispatch_once 是 GCD(Grand Central Dispatch)中的一个函数,用于确保某个代码块只会被执行一次。在多线程环境下,dispatch_once 可以保证代码块只会在第一次调用时执行,后续的调用会被忽略。

dispatch_once 函数的原型如下:

void dispatch_once(dispatch_once_t *predicate, dispatch_block_t block);

使用方法如下:

  1. 定义一个全局的 dispatch_once_t 变量,用于标记代码块是否已经执行过。
  2. 调用 dispatch_once 函数,传入上述变量和要执行的代码块。

示例代码如下:

static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
    // 只会执行一次的代码块
    NSLog(@"This will only be executed once");
});

dispatch_once 的特点和用法如下:

  • 线程安全:dispatch_once 是线程安全的,可以在多线程环境下正确地保证代码块只会执行一次。
  • 性能高效:dispatch_once 使用了一种高效的方式来实现只执行一次的功能,避免了不必要的性能开销。
  • 适用场景:适用于需要在程序运行过程中只执行一次的初始化代码,比如单例模式的创建。

总之,dispatch_once 是一个非常实用的 GCD 函数,可以帮助我们实现一次性初始化等场景下的代码保护和线程安全操作。

6.2 dispatch_after 延迟执行方法

dispatch_after 是 GCD(Grand Central Dispatch)中的一个函数,用于延迟执行任务。通过 dispatch_after 函数,我们可以在指定的时间后执行一个代码块,从而实现延迟执行的效果。

dispatch_after 函数的原型如下:

void dispatch_after(dispatch_time_t when, dispatch_queue_t queue, dispatch_block_t block);

使用方法如下:

  1. 创建一个 dispatch_time_t 对象,用于指定延迟的时间。可以使用 dispatch_time 函数来创建,也可以使用 dispatch_time_delay 函数来指定延迟的秒数。
  2. 调用 dispatch_after 函数,传入延迟时间、执行任务的队列和要执行的代码块。

示例代码如下:

dispatch_time_t delayTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC));
dispatch_after(delayTime, dispatch_get_main_queue(), ^{
    // 2秒后在主队列中执行的代码块
    NSLog(@"This will be executed after 2 seconds");
});

dispatch_after 的特点和用法如下:

  • 灵活性:可以精确控制代码块的延迟执行时间。
  • 可读性:通过使用 dispatch_time 函数创建延迟时间,可以清晰地表达延迟的时长。
  • 适用场景:适用于需要延迟执行任务的场景,比如实现延迟加载、动画效果等。

总之,dispatch_after 是一个方便实用的 GCD 函数,可以帮助我们实现延迟执行任务的需求,提升代码的灵活性和可读性。

6.3 dispatch_barrier_async 栅栏函数

dispatch_barrier_async 是 GCD(Grand Central Dispatch)中的一个函数,用于向指定的并发队列中提交一个 barrier 任务。Barrier 任务会等待在它之前提交的任务执行完成后才会执行,并且会等待它自己的任务执行完成后再继续执行后续的任务。

dispatch_barrier_async 函数的原型如下:

void dispatch_barrier_async(dispatch_queue_t queue, dispatch_block_t block);

使用方法如下:

  1. 创建一个自定义的并发队列,确保队列是通过 dispatch_queue_create 函数创建的,并且设置为并发队列。
  2. 调用 dispatch_barrier_async 函数,传入自定义的并发队列和要执行的 barrier 任务代码块。

示例代码如下:

dispatch_queue_t customQueue = dispatch_queue_create("com.example.barrierQueue", DISPATCH_QUEUE_CONCURRENT);
dispatch_barrier_async(customQueue, ^{
    // barrier 任务代码块
    NSLog(@"Barrier task executed");
});

dispatch_barrier_async 的特点和用法如下:

  • 保证顺序:保证 barrier 任务会在并发队列中的其他任务执行完成后才会执行,且 barrier 任务执行完后才会继续执行后续的任务。
  • 数据同步:适用于需要在读写数据时保证数据同步的场景,可以避免读写数据时出现竞争条件。
  • 性能优化:可以提高并发队列中读写操作的性能,确保写操作不会受到读操作的影响。

总之,dispatch_barrier_async 是一个非常有用的 GCD 函数,可以帮助我们实现在并发队列中执行 barrier 任务,从而保证任务的顺序和数据的同步性。

6.4 dispatch_semaphore 信号量

在 Objective-C 中,GCD(Grand Central Dispatch)提供了 dispatch_semaphore 信号量来控制并发访问的线程数量。dispatch_semaphore 可以用来实现线程同步和资源管理,允许指定数量的线程同时访问共享资源。

以下是 dispatch_semaphore 的介绍和使用方法:

(1) dispatch_semaphore介绍
dispatch_semaphore 是一个信号量,用于控制并发访问的线程数量。
它有两个主要操作:dispatch_semaphore_waitdispatch_semaphore_signal

  • dispatch_semaphore_wait:当信号量的值大于等于 1 时,会将信号量的值减 1,并立即返回。如果信号量的值为 0,则会阻塞当前线程,直到有其他线程调用 dispatch_semaphore_signal 使信号量的值增加为非零。
  • dispatch_semaphore_signal:将信号量的值加 1,唤醒一个被阻塞在 dispatch_semaphore_wait 上的线程。

(2) 使用示例:
下面是一个示例代码,演示了如何使用 dispatch_semaphore 控制并发访问的线程数量:

dispatch_semaphore_t semaphore = dispatch_semaphore_create(1); // 创建信号量,初始值为 1
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

for (int i = 0; i < 5; i++) {
    dispatch_async(queue, ^{
        dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER); // 等待信号量
        NSLog(@"Task %d started", i);
        sleep(1); // 模拟任务执行
        NSLog(@"Task %d finished", i);
        dispatch_semaphore_signal(semaphore); // 释放信号量
    });
}

在上面的示例中,首先创建了一个初始值为 1 的 dispatch_semaphore。然后使用 dispatch_async 在全局队列中执行了 5 个任务,每个任务在开始时调用 dispatch_semaphore_wait 等待信号量,表示占用一个资源;在任务执行完成后调用 dispatch_semaphore_signal 释放信号量,表示释放资源。

通过使用 dispatch_semaphore,可以实现对共享资源的并发访问控制,限制同时访问资源的线程数量,从而避免竞态条件和数据竞争问题。