iOS面试:4.多线程GCD

一、多线程基础知识

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(), ^{//回到主线程刷新UINSLog(@"回到主线程刷新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中执行Blockdispatch_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, ^{// 任务1NSLog(@"Task 1 executed");
});dispatch_group_async(group, queue, ^{// 任务2NSLog(@"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, ^{// 任务1NSLog(@"Task 1 executed");dispatch_group_leave(group);
});dispatch_group_enter(group);
dispatch_async(queue, ^{// 任务2NSLog(@"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, ^{// 任务1NSLog(@"Task 1 executed");dispatch_group_leave(group);
});dispatch_group_enter(group);
dispatch_async(queue, ^{// 任务2NSLog(@"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,可以实现对共享资源的并发访问控制,限制同时访问资源的线程数量,从而避免竞态条件和数据竞争问题。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/264422.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

http协议基础与Apache的简单介绍

一、相关介绍&#xff1a; 互联网&#xff1a;是网络的网络&#xff0c;是所有类型网络的母集因特网&#xff1a;世界上最大的互联网网络。即因特网概念从属于互联网概念。习惯上&#xff0c;大家把连接在因特网上的计算机都成为主机。万维网&#xff1a;WWW&#xff08;world…

二叉树和堆

二叉树和堆 一、树的概念和结构二、二叉树的概念三、堆四、堆的创建one、堆的插入(需要与向上或者向下调整算法配合(取决于你建大堆还是小堆)two、剔除堆中的根节点 五、堆的简单排序 一、树的概念和结构 树是一种非线性的的数据结构&#xff0c;逻辑结构就是一颗倒挂的树&…

Linux使用Docker部署Nacos容器并结合内网穿透实现公网访问本地服务

文章目录 推荐1. Docker 运行Nacos2. 本地访问Nacos3. Linux安装Cpolar4. 配置Nacos UI界面公网地址5. 远程访问 Nacos UI界面6. 固定Nacos UI界面公网地址7. 固定地址访问Plik 推荐 前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff…

精美的WordPress外贸独立站模板

WordPress外贸独立站主题 简洁实用的WordPress外贸独立站主题&#xff0c;适合时尚服装行业搭建wordpress企业官网使用。 https://www.jianzhanpress.com/?p4999 简洁wordpress独立站模板 绿色精美、简洁大气的wordpress外贸独立网站模板 https://www.jianzhanpress.com/?…

本地配置多个git账户及ll设置

本地配置多个git账户 清除全局配置将命令行&#xff0c;切换到ssh目录生成GitLab和Gitee的公钥、私钥去对应的代码仓库添加 SSH Keys添加私钥ll设置 管理密钥验证仓库配置关于gitgitee.com: Permission denied (publickey) 清除全局配置 此步骤可以不做&#xff0c;经测试不影…

微信小程序本地开发

微信小程序本地开发时不需要在小程序后台配置服务器域名直接在小程序项目中填写后端在本机的IP地址和端口号 如图&#xff08;第一步&#xff09; 填写地址后发现报错&#xff0c;url不是合法域名&#xff0c;则在详情设置不校验合法域名 如图&#xff08;第二歩&#xff09;…

AI:134-基于深度学习的社交媒体图像内容分析

🚀点击这里跳转到本专栏,可查阅专栏顶置最新的指南宝典~ 🎉🎊🎉 你的技术旅程将在这里启航! 从基础到实践,深入学习。无论你是初学者还是经验丰富的老手,对于本专栏案例和项目实践都有参考学习意义。 ✨✨✨ 每一个案例都附带有在本地跑过的关键代码,详细讲解供…

猫头虎分享已解决Bug || AttributeError: ‘Sequential‘ object has no attribute ‘session‘

博主猫头虎的技术世界 &#x1f31f; 欢迎来到猫头虎的博客 — 探索技术的无限可能&#xff01; 专栏链接&#xff1a; &#x1f517; 精选专栏&#xff1a; 《面试题大全》 — 面试准备的宝典&#xff01;《IDEA开发秘籍》 — 提升你的IDEA技能&#xff01;《100天精通鸿蒙》 …

Remainder Problem(根号分治)

Educational Codeforces Round 71 (Rated for Div. 2) F. Remainder Problem 题目链接 F. Remainder Problem 题意&#xff1a; 给你一个由 500000 500000 500000 个整数&#xff08;编号从 1 1 1 到 500000 500000 500000 &#xff09;组成的数组 a a a 。最初 a a a…

SpringBoot -【SmartInitializingSingleton】基础使用及应用场景

SmartInitializingSingleton 在继续深入探讨 SmartInitializingSingleton接口之前&#xff0c;让我们先了解一下 Spring Framework 的基本概念和背景。Spring Framework 是一个开源的 JavaEE&#xff08;Java Enterprise Edition&#xff09;全栈&#xff08;full-stack&#x…

PureFlash v1.9.1特性介绍

PureFlashv1.9.1版本特性主要有3个&#xff1a; 1. 支持RDMA网络 使用RDMA协议可以大大减少对CPU的消耗&#xff0c;性能提升30%以上。 PureFlash的网络配置分为存储节点间网络&#xff08;存储后端网&#xff09;和客户端网络&#xff08;前端网&#xff09;。都支持使用RD…

Java的编程之旅19——使用idea对面相对象编程项目的创建

在介绍面向对象编程之前先说一下我们在idea中如何创建项目文件 使用快捷键CtrlshiftaltS新建一个模块&#xff0c;点击“”&#xff0c;再点New Module 点击Next 我这里给Module起名叫OOP,就是面向对象编程的英文缩写&#xff0c;再点击下面的Finish 点Apply或OK均可 右键src…

网络设备和网络软件

文章目录 网络设备和网络软件网卡交换机交换机的三个主要功能交换机的工作原理第二层交换和第三层交换交换机的堆叠和级联 路由器路由器工作原理 网关网关的分类 无线接入点(AP)调制解调器网络软件 网络设备和网络软件 网卡 网络接口卡又称网络适配器&#xff0c;简称网卡。网…

MySQL数据库基础(十五):PyMySQL使用介绍

文章目录 PyMySQL使用介绍 一、为什么要学习PyMySQL 二、安装PyMySQL模块 三、PyMySQL的使用 1、导入 pymysql 包 2、创建连接对象 3、获取游标对象 4、pymysql完成数据的查询操作 5、pymysql完成对数据的增删改 PyMySQL使用介绍 提前安装MySQL数据库&#xff08;可以…

服务器防漏扫

什么是漏扫&#xff1f; 漏扫是漏洞扫描的简称。漏洞扫描是一种安全测试方法&#xff0c;用于发现计算机系统、网络或应用程序中的潜在漏洞和安全弱点。通过使用自动化工具或软件&#xff0c;漏洞扫描可以检测系统中存在的已知漏洞&#xff0c;并提供相关的报告和建议&#xf…

记阿里云mysql丢表丢数据的实践记录

第一时间挂工单&#xff0c;联系工程师指引&#xff0c;现在回过来想&#xff0c;第一时间要确认发生时间。 1.通过性能视图&#xff08;马后炮的总结&#xff0c;实际凭记忆恢复了三四次才找到数据&#xff09; 2.先恢复数据 通过Navicat工具&#xff0c;结构同步&#xff0…

【Docker】03 容器操作

文章目录 一、流转图二、基本操作2.1 查看本地容器进程2.2 启动容器2.2.1 交互式启动容器2.2.2 后台启动容器 2.3 进入容器2.4 停止启动重启容器2.5 退出容器2.6 删除容器2.7 提交容器&#xff08;打包成镜像&#xff09;2.8 拷贝文件2.8.1 拷贝容器内文件到宿主机2.8.2 拷贝宿…

[TCP] TCP/IP 基础知识词典(2)

我想统计一下&#xff0c;TCP/IP 尤其是TCP协议&#xff0c;能搜到的常见的问题&#xff0c;整理起来&#xff0c;关键词添加在目录中&#xff0c;便于以后查阅。 目前预计整理共3篇&#xff1a; [TCP] TCP/IP 基础知识问答 &#xff1a;基础知识 [TCP] TCP/IP 基础知识问答&…

MATLAB Function转C代码实战

文章目录 前言1. 准备工作2. 使用MATLAB Coder2.1 确定输入输出的类型2.2 MATLAB Coder过程 3. 代码调整和优化4. 编译和测试5. 性能分析和优化结语 前言 在科学与工程领域&#xff0c;MATLAB&#xff08;Matrix Laboratory&#xff09;是一种广泛使用的高级技术计算软件&…

Python爬虫技术详解:从基础到高级应用,实战与应对反爬虫策略【第93篇—Python爬虫】

前言 随着互联网的快速发展&#xff0c;网络上的信息爆炸式增长&#xff0c;而爬虫技术成为了获取和处理大量数据的重要手段之一。在Python中&#xff0c;requests模块是一个强大而灵活的工具&#xff0c;用于发送HTTP请求&#xff0c;获取网页内容。本文将介绍requests模块的…