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.mzph.cn/news/699541.shtml

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

相关文章

算法题目中图和树的存储

邻接表的方式存储图和树 这就是邻接表&#xff0c;就是将每个结点的孩子结点用链表表示出来&#xff0c;再将所有结点以数组形式连起来。 存储树和图我们需要三个数组&#xff0c;h[N], e[N], ne[N],分别表示邻接表&#xff0c;结点值&#xff0c;结点的next值&#xff0c;h[i…

面试经典150题——快乐数

​"Success is not final, failure is not fatal: It is the courage to continue that counts." - Winston Churchill 1. 题目描述 2. 题目分析与解析 2.1 思路一 还是最简单的&#xff0c;模拟最直观的思路&#xff0c;就是进行一个while循环。比如&#xff1a;…

yolov8-seg dnn调用

接上篇一直更换torch、opencv版本都无法解决这个问题&#xff08;seg调用dnn报错&#xff09;。那问题会不会出在yolov8源码本身呢。yolov8的讨论区基本都看过了&#xff0c;我决定尝试在其前身yolov5的讨论区上找找我不信没人遇到这个问题。很快找到下面的讨论第一个帖子&…

20个改善编码的Python异常处理技巧,让你的代码更高效

异常处理是写好代码的一个重要的方面&#xff0c;虽然许多开发人员都熟悉基本的try-except块&#xff0c;但是有很多更深入的知识可以使异常处理更高效、更可读和更python化。所以本文将介绍关于Python异常的20个可以显著改善编码的Python异常处理技巧&#xff0c;这些技巧可以…

软件性能测试和功能测试有何联系和区别?第三方软件检测机构简析

软件性能测试和功能测试是软件开发过程中非常重要的两个环节。从根本上说&#xff0c;它们都是为了保证软件质量和可靠性&#xff0c;但它们的目标和方法却有所不同。 软件性能测试是评估软件在特定负载下的性能表现&#xff0c;包括响应时间、吞吐量、并发能力等指标。它通过…

Jenkins详解

目录 一、Jenkins CI/CD 1、 Jenkins CI/CD 流程图 2、介绍 Jenkins 1、Jenkins概念 2、Jenkins目的 3、特性 4、产品发布流程 3、安装Jenkins 1、安装JDK 2、安装tomcat 3.安装maven 4安装jenkins 5.启动tomcat&#xff0c;并页面访问 5.添加节点 一、Jenkins CI/…

Opencv实战(2)绘图与图像操作

Opencv实战(2)绘图与图像操作 指路前文&#xff1a;Opencv实战(1)读取与像素操作 三、基本绘图 文章目录 Opencv实战(2)绘图与图像操作三、基本绘图(1).line(2).rectangle(3).circle 四、图像处理(1).颜色空间1.意义2.cvtColor()3.inRange()4.适应光线 (2).形态操作1.腐蚀2.膨…

Python语句(一)【条件语句】

条件语句&#xff1a;通过一条或多条语句的执行结果&#xff08;True或者False&#xff09;来决定执行的代码块。 其程序流程图如下&#xff1a; 条件语句包括&#xff1a;if 判断条件&#xff1a;执行语句…… else&#xff1a;执行语句……orif 判断条件1:执行语句1…… el…

电商+支付双系统项目------项目部署到服务器

我已经把这个项目的所有模块都做好了。那么&#xff0c;现在我们要做的就是将这个项目部署发布了。其实关于部署发布网上有很多的文章都会教&#xff0c;我就不写哪些很具体的步骤了&#xff0c;我就简单的总结一下怎么部署这个项目&#xff0c;让大家对项目部署有一个整体的认…

kubernetes的网络flannel与caclio

flannel网络 跨主机通信的一个解决方案是Flannel&#xff0c;由CoreOS推出&#xff0c;支持3种实现&#xff1a;UDP、VXLAN、host-gw udp模式&#xff1a;使用设备flannel.0进行封包解包&#xff0c;不是内核原生支持&#xff0c;上下文切换较大&#xff0c;性能非常差 vxlan模…

瑞_23种设计模式_装饰者模式

文章目录 1 装饰者模式&#xff08;Decorator Pattern&#xff09;1.1 介绍1.2 概述1.3 装饰者模式的结构 2 案例一2.1 需求2.2 代码实现 3 案例二3.1 需求3.2 代码实现 4 JDK源码解析5 总结5.1 装饰者模式的优缺点5.2 装饰者模式的使用场景5.3 装饰者模式 VS 代理模式 &#x…

dpdk环境搭建和工作原理

文章目录 1、DPDK环境搭建1.1、环境搭建1.2、编译DPDK 2、DPDK工作原理 1、DPDK环境搭建 1.1、环境搭建 工具准备&#xff1a;VMware、ubuntu16.04。 &#xff08;1&#xff09;VMware添加两个网卡。桥接网卡作为 DPDK 运行的网卡&#xff0c;NAT 网卡作为 ssh 连接的网卡。 …

【动态规划】【前缀和】【推荐】2463. 最小移动总距离

作者推荐 【广度优先搜索】【网格】【割点】【 推荐】1263. 推箱子 本文涉及知识点 动态规划汇总 C算法&#xff1a;前缀和、前缀乘积、前缀异或的原理、源码及测试用例 包括课程视频 2463. 最小移动总距离 X 轴上有一些机器人和工厂。给你一个整数数组 robot &#xff0c…

【统计分析数学模型】判别分析(四):机器学习分类算法

【统计分析数学模型】判别分析&#xff08;四&#xff09;&#xff1a;机器学习分类算法 一、机器学习分类算法1. 交叉验证方法2. 案例数据集3. 数据标准化 二、决策树模型1. 基本原理2. 计算步骤3. R语言实现 三、K最邻近分类1. 基本原理2. K值的选择3. R语言实现 四、支持向量…

5分钟JavaScript快速入门

目录 一.JavaScript基础语法 二.JavaScript的引入方式 三.JavaScript中的数组 四.BOM对象集合 五.DOM对象集合 六.事件监听 使用addEventListener()方法添加事件监听器 使用onX属性直接指定事件处理函数 使用removeEventListener()方法移除事件监听器 一.JavaScript基础…

投屏软件Airserver优惠码来了,使用能减10元(有图有真相)

Airserver是一款非常实用的手机投屏到电脑软件。AirServer for Mac是一款能够通过本地网络将音频、照片、视频以及支持AIrPlay功能的第三方App&#xff0c;从 iOS 设备无线传送到 Mac 电脑的屏幕上&#xff0c;把Mac变成一个AirPlay终端的实用工具。 Airserver中文官网地址&…

【算法与数据结构】回溯算法、贪心算法、动态规划、图论(笔记三)

文章目录 七、回溯算法八、贪心算法九、动态规划9.1 背包问题9.2 01背包9.3 完全背包9.4 多重背包 十、图论10.1 深度优先搜索10.2 广度优先搜索10.3 并查集 最近博主学习了算法与数据结构的一些视频&#xff0c;在这个文章做一些笔记和心得&#xff0c;本篇文章就写了一些基础…

【C++】类和对象---友元,内部类,匿名对象详解

目录 友元 友元函数 友元类 内部类 匿名对象 ⭐友元 友元提供了一种突破封装的方式&#xff0c;有时提供了便利。但是友元会增加耦合度&#xff0c;破坏了封装&#xff0c;所以 友元不宜多用。 友元分为&#xff1a;友元函数和友元类。 ⚡友元函数 先看一个问题&#x…

使用 yarn 的时候,遇到 Error [ERR_REQUIRE_ESM]: require() of ES Module 怎么解决?

晚上回到家&#xff0c;我打开自己的项目&#xff0c;执行&#xff1a; cd HexoPress git pull --rebase yarn install yarn dev拉取在公司 push 的代码&#xff0c;然后更新依赖&#xff0c;最后开始今晚的开发时候&#xff0c;意外发生了&#xff0c;竟然报错了&#xff0c;…

Python流程控制有知道的吗?

流程控制是编程的核心概念之一&#xff0c;Python也不例外。在Python中&#xff0c;程序的流程控制结构主要包括顺序结构、选择结构和循环结构。这些结构让程序员能够更好地组织代码&#xff0c;使其按照特定的逻辑执行。 1.顺序结构 顺序结构是Python中最简单的流程控制结构&…