【iOS】——GCD再学习

文章目录

  • 一、GCD的定义
  • 二、GCD 任务和队列
    • 1.任务
    • 2.队列
  • 三、GCD 的使用
    • 1.创建队列
    • 2.创建任务
    • 3.队列+任务 组合方式
    • 并发队列 + 同步执行
    • 异步执行 + 并发队列
    • 同步执行 + 串行队列
    • 异步执行 + 串行队列
    • 同步执行 + 主队列
      • 在主线程中调用 同步执行 + 主队列
      • 在其它线程中调用 同步执行 + 主队列
    • 异步执行 + 主队列
    • 4.同一队列嵌套+任务 组合方式
  • 五、GCD通信
  • 六、GCD 的API
    • 1.dispatch_set_target_queue(设置目标队列)
    • 2.dispatch_after(设置延迟执行)
    • 3.Dispatch Group(队列组)
      • dispatch_group_notify
      • dispatch_group_wait
      • dispatch_group_enter、dispatch_group_leave
    • 4.dispatch_barrier_async(栅栏方法)
    • 5.dispatch_once(限制一次方法)
    • 6.dispatch_apply(快速迭代方法)
    • 7.dispatch_suspend & dispatch_resume(暂停和恢复)
    • 8.dispatch_semaphore(信号量)


一、GCD的定义

Grand Central Dispatch(GCD)是异步执行任务的技术之一。一般将应用程序中记述的线程管理用的代码在系统级中实现。开发者只需要定义想执行的任务并追加到适当的Dispatch Queue中,GCD就能生成必要的线程并计划执行任务。由于线程管理是作为系统的一部分来实现的,因此可统一管理,也可执行任务,这样就比以前的线程更有效率。

例如:

//让处理在后台线程中执行
dispatch async(queue, ^{/**长时间处理*例如AR用画像识别*例如数据库访问*//**长时间处理结束, 主线程使用该处理结果。 *///让处理在主线程中执行dispatch_async(dispatch_get main_queue(), ^{/**只在主线程可以执行的处理*例如用户界面更新*/});
});

GCD的优点如下:

  • GCD 可用于多核的并行运算;
  • GCD 会自动利用更多的 CPU 内核(比如双核、四核);
  • GCD 会自动管理线程的生命周期(创建线程、调度任务、销毁线程);
  • 程序员只需要告诉 GCD 想要执行什么任务,不需要编写任何线程管理代码。

二、GCD 任务和队列

在 GCD 中两个核心概念:任务队列

1.任务

任务:是执行的处理,也就是你在线程中执行的那段代码。在GCD中是放在block中,通过dispatch_async函数追加赋值在变量queue的“Dispatch Queue”中。执行任务有两种方式:同步执行和异步执行。两者的主要区别是:是否具备开启新线程的能力。

  • 同步执行:同步添加任务到指定的队列中,在添加的任务执行结束之前,会一直等待,直到队列里面的任务完成之后再继续执行。并且只能在当前线程中执行任务,不具备开启新线程的能力。
  • 异步执行:异步添加任务到指定的队列中,它不会做任何等待,可以继续执行任务。并且可以在新的线程中执行任务,具备开启新线程的能力。

注意:异步执行(async):虽然具有开启新线程的能力,但是并不一定开启新线程。这跟任务所指定的队列类型有关

2.队列

队列(Dispatch Queue):这里的队列指执行任务的等待队列,即用来存放任务的队列。队列是一种特殊的线性表,采用 FIFO(先进先出)的原则,即新任务总是被插入到队列的末尾,而读取任务的时候总是从队列的头部开始读取。每读取一个任务,则从队列中释放一个任务。如下图所示:

在这里插入图片描述

在GCD中有两种队列:串行队列和并发队列。

  • 串行队列(Serial Dispatch Queue):等待现在执行中的处理,也就是每次只有一个任务被执行。让任务一个接着一个地执行。(只开启一个线程,一个任务执行完毕后,再执行下一个任务)
  • 并发队列(Concurrent Dispatch Queue):不等待现在执行中的处理,也就是可以让多个任务并发(同时)执行。(可以开启多个线程,并且同时执行任务)

并发功能只有在异步(dispatch_async)函数下才有效

两者的区别如下图所示:
在这里插入图片描述

串行队列使用条件之一:
避免多个线程更新相同资源导致数据竞争
并且生成串行队列的个数应当仅限所必需的个数,避免不必要的资源开销


三、GCD 的使用

1.创建队列

使用dispatch_queue_create来创建队列,需要传入两个参数第一个参数表示队列的唯一标识符,用于DEBUG,可为空第二个参数用来识别是串行队列还是并发队列。DISPATCH_QUEUE_SERIAL或NULL表示串行队列,DISPATCH_QUEUE_CONCURRENT表示并发队列。
代码如下:

// 串行队列的创建方法
dispatch_queue_t queue= dispatch_queue_create("test.queue", DISPATCH_QUEUE_SERIAL);
// 并发队列的创建方法
dispatch_queue_t queue= dispatch_queue_create("test.queue", DISPATCH_QUEUE_CONCURRENT);

在MRC中通过dispatch_queue_create函数生成的Dispatch Queue在使用结束后通过dispatch_release函数释放

dispatch_release(queue);

对于串行队列,GCD 默认提供了:主队列(Main Dispatch Queue)。
所有放在主队列中的任务,都会放到主线程中执行。
可使用 dispatch_get_main_queue() 方法获得主队列。

主队列的实质上就是一个普通的串行队列,只是因为默认情况下,当前代码是放在主队列中的,然后主队列中的代码,又都会放到主线程中去执行,所以才造成了主队列特殊的现象。

// 主队列的获取方法
dispatch_queue_t queue = dispatch_get_main_queue();

对于并发队列,GCD 默认提供:全局并发队列(Global Dispatch Queue)。
可以使用 dispatch_get_global_queue 方法来获取全局并发队列。需要传入两个参数。第一个参数表示队列优先级,默认为 DISPATCH_QUEUE_PRIORITY_DEFAULT(默认优先级)。第二个参数暂时没用,用 0 即可。

Global Dispatch Queue有4个执行优先级:分别是高优先级(High Priority)、默认优先级(Default Priority)、低优先级(Low Priority)和后台优先级(Background Priority)。

代码如下:

// 全局并发队列的获取方法
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

2.创建任务

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

代码如下:

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

3.队列+任务 组合方式

我们知道有两种任务,加上主队列有三种队列,那么就有了6种组合方式:

  1. 并发队列 + 同步执行
  2. 并发队列 + 异步执行
  3. 串行队列 + 同步执行
  4. 串行队列 + 异步执行
  5. 主队列 + 同步执行
  6. 主队列 + 异步执行

下面是它们的区别:
在这里插入图片描述

主线程 中调用 主队列+同步执行 会导致死锁问题。
这是因为 主队列中追加的同步任务主线程本身的任务 两者之间相互等待,阻塞了 主队列,最终造成了主队列所在的线程(主线程)死锁问题。
而如果我们在 其他线程 调用 主队列+同步执行,则不会阻塞 主队列,自然也不会造成死锁问题。最终的结果是:不会开启新线程,串行执行任务。

并发队列 + 同步执行

在当前线程中执行任务,不会开启新线程,执行完一个任务,再执行下一个任务

/*** 同步执行 + 并发队列* 特点:在当前线程中执行任务,不会开启新线程,执行完一个任务,再执行下一个任务。*/
- (void)syncConcurrent {NSLog(@"currentThread---%@",[NSThread currentThread]);  // 打印当前线程NSLog(@"syncConcurrent---begin");dispatch_queue_t queue = dispatch_queue_create("net.bujige.testQueue", DISPATCH_QUEUE_CONCURRENT);dispatch_sync(queue, ^{// 追加任务 1[NSThread sleepForTimeInterval:2];              // 模拟耗时操作NSLog(@"1---%@",[NSThread currentThread]);      // 打印当前线程});dispatch_sync(queue, ^{// 追加任务 2[NSThread sleepForTimeInterval:2];              // 模拟耗时操作NSLog(@"2---%@",[NSThread currentThread]);      // 打印当前线程});dispatch_sync(queue, ^{// 追加任务 3[NSThread sleepForTimeInterval:2];              // 模拟耗时操作NSLog(@"3---%@",[NSThread currentThread]);      // 打印当前线程});NSLog(@"syncConcurrent---end");
}

在这里插入图片描述

  • 所有任务都是在当前线程(主线程)中执行的,没有开启新的线程(同步执行不具备开启新线程的能力)。
  • 所有任务都在打印的 syncConcurrent—begin 和 syncConcurrent—end 之间执行的(同步任务 需要等待队列的任务执行结束)。
  • 任务按顺序执行的。按顺序执行的原因:虽然 并发队列 可以开启多个线程,并且同时执行多个任务。但是因为本身不能创建新线程,只有当前线程这一个线程(同步任务不具备开启新线程的能力),所以也就不存在并发。而且当前线程只有等待当前队列中正在执行的任务执行完毕之后,才能继续接着执行下面的操作(同步任务需要等待队列的任务执行结束)。所以任务只能一个接一个按顺序执行,不能同时被执行。

异步执行 + 并发队列

可以开启多个线程,任务交替(同时)执行。

/*** 异步执行 + 并发队列* 特点:可以开启多个线程,任务交替(同时)执行。*/
- (void)asyncConcurrent {NSLog(@"currentThread---%@",[NSThread currentThread]);  // 打印当前线程NSLog(@"asyncConcurrent---begin");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_async(queue, ^{// 追加任务 3[NSThread sleepForTimeInterval:2];              // 模拟耗时操作NSLog(@"3---%@",[NSThread currentThread]);      // 打印当前线程});NSLog(@"asyncConcurrent---end");
}

在这里插入图片描述

异步执行 具备开启新线程的能力。且 并发队列 可开启多个线程,同时执行多个任务
所有任务是在打印的 syncConcurrent—begin 和 syncConcurrent—end 之后才执行的。说明当前线程没有等待,而是直接开启了新线程,在新线程中执行任务(异步执行 不做等待,可以继续执行任务)。

同步执行 + 串行队列

不会开启新线程,在当前线程执行任务。任务是串行的,执行完一个任务,再执行下一个任务。

/*** 同步执行 + 串行队列* 特点:不会开启新线程,在当前线程执行任务。任务是串行的,执行完一个任务,再执行下一个任务。*/
- (void)syncSerial {NSLog(@"currentThread---%@",[NSThread currentThread]);  // 打印当前线程NSLog(@"syncSerial---begin");dispatch_queue_t queue = dispatch_queue_create("net.bujige.testQueue", DISPATCH_QUEUE_SERIAL);dispatch_sync(queue, ^{// 追加任务 1[NSThread sleepForTimeInterval:2];              // 模拟耗时操作NSLog(@"1---%@",[NSThread currentThread]);      // 打印当前线程});dispatch_sync(queue, ^{// 追加任务 2[NSThread sleepForTimeInterval:2];              // 模拟耗时操作NSLog(@"2---%@",[NSThread currentThread]);      // 打印当前线程});dispatch_sync(queue, ^{// 追加任务 3[NSThread sleepForTimeInterval:2];              // 模拟耗时操作NSLog(@"3---%@",[NSThread currentThread]);      // 打印当前线程});NSLog(@"syncSerial---end");
}

在这里插入图片描述

所有任务都是在当前线程(主线程)中执行的,并没有开启新的线程(同步执行 不具备开启新线程的能力)。
所有任务都在打印的 syncConcurrent—begin 和 syncConcurrent—end 之间执行(同步任务 需要等待队列的任务执行结束)。
任务是按顺序执行的(串行队列 每次只有一个任务被执行,任务一个接一个按顺序执行)。

异步执行 + 串行队列

会开启新线程,但是因为任务是串行的,执行完一个任务,再执行下一个任务

/*** 异步执行 + 串行队列* 特点:会开启新线程,但是因为任务是串行的,执行完一个任务,再执行下一个任务。*/
- (void)asyncSerial {NSLog(@"currentThread---%@",[NSThread currentThread]);  // 打印当前线程NSLog(@"asyncSerial---begin");dispatch_queue_t queue = dispatch_queue_create("net.bujige.testQueue", DISPATCH_QUEUE_SERIAL);dispatch_async(queue, ^{// 追加任务 1[NSThread sleepForTimeInterval:2];              // 模拟耗时操作NSLog(@"1---%@",[NSThread currentThread]);      // 打印当前线程});dispatch_async(queue, ^{// 追加任务 2[NSThread sleepForTimeInterval:2];              // 模拟耗时操作NSLog(@"2---%@",[NSThread currentThread]);      // 打印当前线程});dispatch_async(queue, ^{// 追加任务 3[NSThread sleepForTimeInterval:2];              // 模拟耗时操作NSLog(@"3---%@",[NSThread currentThread]);      // 打印当前线程});NSLog(@"asyncSerial---end");
}

在这里插入图片描述

开启了一条新线程(异步执行 具备开启新线程的能力,串行队列 只开启一个线程)。
所有任务是在打印的 syncConcurrent—begin 和 syncConcurrent—end 之后才开始执行的(异步执行 不会做任何等待,可以继续执行任务)。
任务是按顺序执行的(串行队列 每次只有一个任务被执行,任务一个接一个按顺序执行)。

同步执行 + 主队列

同步执行 + 主队列 在不同线程中调用结果也是不一样,在主线程中调用会发生死锁问题,而在其他线程中调用则不会。

在主线程中调用 同步执行 + 主队列

- (void)syncMain {     //主队列 + 同步执行NSLog(@"syncMain---begin");dispatch_queue_t queue = dispatch_get_main_queue();dispatch_sync(queue, ^{for (int i = 0; i < 2; ++i) {NSLog(@"1------%@", [NSThread currentThread]);}});dispatch_sync(queue, ^{for (int i = 0; i < 2; ++i) {NSLog(@"2------%@", [NSThread currentThread]);}});dispatch_sync(queue, ^{for (int i = 0; i < 2; ++i) {NSLog(@"3------%@", [NSThread currentThread]);}});NSLog(@"syncMain---end");
}

在这里插入图片描述
运行发现会报错,原因如下:

这段代码是在主线程中运行,而在主线程中将任务放在了主队列中,同步执行有个特点,就是对于任务是立马执行的。那么当我们把第一个任务放进主队列中,它就会立马执行。但是主线程现在正在处理syncMain方法,所以任务需要等syncMain执行完才能执行。而syncMain执行到第一个任务的时候,又要等第一个任务执行完才能往下执行第二个和第三个任务。
那么,现在的情况就是syncMain方法和第一个任务都在等对方执行完毕。这样大家互相等待,所以就卡住了,任务执行不了,并且最后syncMain—end也没有打印

在其它线程中调用 同步执行 + 主队列

不会开启新线程,执行完一个任务,再执行下一个任务

//将方法调用改为
dispatch_queue_t queue = dispatch_queue_create("test.queue", DISPATCH_QUEUE_CONCURRENT);dispatch_async(queue, ^{[self syncMain];
});

在这里插入图片描述

在其他线程中使用主队列 + 同步执行可看到:所有任务都是在主线程中执行的,并没有开启新的线程。而且由于主队列是串行队列,所以按顺序一个一个执行。
所有任务都在打印的syncConcurrent—begin和syncConcurrent—end之间,这说明任务是添加到队列中马上执行的。

异步执行 + 主队列

只在主线程中执行任务,执行完一个任务,再执行下一个任务。

/*** 异步执行 + 主队列* 特点:只在主线程中执行任务,执行完一个任务,再执行下一个任务*/
- (void)asyncMain {NSLog(@"currentThread---%@",[NSThread currentThread]);  // 打印当前线程NSLog(@"asyncMain---begin");dispatch_queue_t queue = dispatch_get_main_queue();dispatch_async(queue, ^{// 追加任务 1[NSThread sleepForTimeInterval:2];              // 模拟耗时操作NSLog(@"1---%@",[NSThread currentThread]);      // 打印当前线程});dispatch_async(queue, ^{// 追加任务 2[NSThread sleepForTimeInterval:2];              // 模拟耗时操作NSLog(@"2---%@",[NSThread currentThread]);      // 打印当前线程});dispatch_async(queue, ^{// 追加任务 3[NSThread sleepForTimeInterval:2];              // 模拟耗时操作NSLog(@"3---%@",[NSThread currentThread]);      // 打印当前线程});NSLog(@"asyncMain---end");
}

在这里插入图片描述

所有任务都在主线程中,虽然是异步执行,具备开启线程的能力,但因为是主队列,所以所有任务都在主线程中,并且一个接一个执行。
所有任务是在打印的 syncConcurrent—begin 和 syncConcurrent—end 之后才开始执行的(异步执行不会做任何等待,可以继续执行任务)
任务是按顺序执行的(因为主队列是 串行队列,每次只有一个任务被执行,任务一个接一个按顺序执行)。

4.同一队列嵌套+任务 组合方式

在使用串行队列的时候,也可能出现阻塞串行队列所在线程的情况发生,从而造成死锁问题。这种情况多见于同一个串行队列的嵌套使用。
比如下面代码这样:在异步执行+串行队列的任务中,又嵌套了当前的串行队列,然后进行同步执行:

dispatch_queue_t queue = dispatch_queue_create("test.queue", DISPATCH_QUEUE_SERIAL);
dispatch_async(queue, ^{    // 异步执行 + 串行队列dispatch_sync(queue, ^{  // 同步执行 + 当前串行队列// 追加任务 1[NSThread sleepForTimeInterval:2];              // 模拟耗时操作NSLog(@"1---%@",[NSThread currentThread]);      // 打印当前线程});
});

下面是同一队列嵌套+任务 组合使用的区别:
在这里插入图片描述

五、GCD通信

在 iOS 开发过程中,我们一般在主线程里边进行 UI 刷新,例如:点击、滚动、拖拽等事件。我们通常把一些耗时的操作放在其他线程,比如说图片下载、文件上传等耗时操作。而当我们有时候在其他线程完成了耗时操作时,需要回到主线程,那么就用到了线程之间的通讯。

/*** 线程间通信*/
- (void)communication {// 获取全局并发队列dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);// 获取主队列dispatch_queue_t mainQueue = dispatch_get_main_queue();dispatch_async(queue, ^{// 异步追加任务 1[NSThread sleepForTimeInterval:2];              // 模拟耗时操作NSLog(@"1---%@",[NSThread currentThread]);      // 打印当前线程// 回到主线程dispatch_async(mainQueue, ^{// 追加在主线程中执行的任务[NSThread sleepForTimeInterval:2];              // 模拟耗时操作NSLog(@"2---%@",[NSThread currentThread]);      // 打印当前线程});});
}

可以看到在其他线程中先执行任务,执行完了之后回到主线程执行主线程的相应操作。

六、GCD 的API

前面已经介绍了GCD中关于创建和获取任务和队列的API,这里就不再重复,下面是关于GCD的其它的API。

1.dispatch_set_target_queue(设置目标队列)

dispatch_queue_create 方法生成的DIspatch Queue不管是串行还是并发队列,都使用和globalQueue的默认优先级相同执行优先级的线程,对于变更优先级我们需要使用dispatch_set_target_queue函数。

第一个参数为要变更优先级的队列,第二个参数为指定的队列所属的优先级(也就是第一个队列变换的优先级)

dispatch_set_target_queue(changedQueue, targetQueue);

dispatch_set_target_queue函数不仅可以变更队列的优先级还可以作为Dispatch Queue的执行阶层。
比如如果在多个串行队列中用dispatch_set_target_queue函数指定目标为某一个串行队列,那么原本要并发执行的多个串行队列现在只能在指定的串行队列上处理,也就变成非并发处理。

2.dispatch_after(设置延迟执行)

可以用 GCD 的dispatch_after 方法来实现在指定时间(例如 1 秒)之后执行某个任务。需要注意的是,dispatch_after 方法并不是在指定时间之后才开始执行处理,而是在指定时间之后将任务追加到主队列中。严格来说,这个时间并不是绝对准确的,但想要大致延迟执行任务,dispatch_after 方法是很有效的。

/*** 延时执行方法 dispatch_after*/
- (void)after {NSLog(@"currentThread---%@",[NSThread currentThread]);  // 打印当前线程NSLog(@"asyncMain---begin");dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{// 2.0 秒后异步追加任务代码到主队列,并开始执行NSLog(@"after---%@",[NSThread currentThread]);  // 打印当前线程});
}

在这里插入图片描述
通过运行结果就可以看出并非是准确的2秒执行任务

3.Dispatch Group(队列组)

当我们想分别异步执行多个耗时任务,然后这几个耗时任务都执行完毕后再回到主线程执行任务。这时候我们可以用到 GCD 的队列组。

使用方法如下:

  1. 调用队列组的 dispatch_group_async 先把任务放到队列中,然后将队列放入队列组中。或者使用队列组的
    dispatch_group_enter、dispatch_group_leave 组合来实现
    dispatch_group_async。
  2. 调用队列组的 dispatch_group_notify 回到指定线程执行任务。或者使用 dispatch_group_wait
    回到当前线程继续向下执行(会阻塞当前线程)。
- (void)useDispatchGroup {dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);dispatch_group_t group = dispatch_group_create();dispatch_group_async(group, queue, ^{NSLog(@"blk0");});dispatch_group_async(group, queue, ^{NSLog(@"blk1");});dispatch_group_async(group, queue, ^{NSLog(@"blk2");});dispatch_group_notify(group, queue, ^{NSLog(@"Done!");});
}

在这里插入图片描述

可以看到由于多个线程并发执行所以任务处理的顺序不定,但是执行打印Done的任务总是会最后执行

dispatch_group_notify

监听 group 中任务的完成状态,当所有的任务都执行完成后,追加任务到 group 中,并执行任务。也就是当所有任务都执行完成之后,才执行 dispatch_group_notify 相关 block 中的任务。
第一个参数为队列组,第二个参数是要追加执行的任务

 dispatch_group_notify(group, dispatch_get_main_queue(), ^{NSLog(@"add task");});

dispatch_group_wait

暂停当前线程(阻塞当前线程),等待指定的 group 中的任务执行完成后,才会往下继续执行。也就是当所有任务执行完成之后,才执行 dispatch_group_wait 之后的操作。但是,使用dispatch_group_wait 会阻塞当前线程。

第一个参数是队列组,第二参数指定为等待的时间(超时),DISPATCH_TIME_FOREVER意味永久等待,也就是队列组尚未结束就不会取消等待。

dispatch_group_wait(group, DISPATCH_TIME_FOREVER);

dispatch_group_enter、dispatch_group_leave

  • dispatch_group_enter 标志着一个任务追加到 group,执行一次,相当于 group 中未执行完毕任务数 +1
  • dispatch_group_leave 标志着一个任务离开了 group,执行一次,相当于 group 中未执行完毕任务数 -1。

当 group 中未执行完毕任务数为0的时候,才会使 dispatch_group_wait 解除阻塞,以及执行追加到 dispatch_group_notify 中的任务。

4.dispatch_barrier_async(栅栏方法)

有时需要有顺序异步执行两组操作,比如第一组操作执行完之后,才能开始执行第二组操作。这样我们就需要一个相当于 栅栏 一样的一个方法将两组异步执行的操作组给分割起来,当然这里的操作组里可以包含一个或多个任务。这就需要用到dispatch_barrier_async 方法在两个操作组间形成栅栏。
dispatch_barrier_async 方法会等待前边追加到并发队列中的任务全部执行完毕之后,再将指定的任务追加到该异步队列中。然后在 dispatch_barrier_async 方法追加的任务执行完毕之后,异步队列才恢复为一般动作,接着追加任务到该异步队列并开始执行。具体如下图所示:

在这里插入图片描述

/*** 栅栏方法 dispatch_barrier_async*/
- (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]);      // 打印当前线程});
}

在这里插入图片描述

在执行完栅栏前面的操作之后,才执行栅栏操作,最后再执行栅栏后边的操作。

5.dispatch_once(限制一次方法)

在创建单例、或者有整个程序运行过程中只执行一次的代码时,我们就用到了 GCD 的 dispatch_once 方法。使用 dispatch_once 方法能保证某段代码在程序运行过程中只被执行 1 次,并且即使在多线程的环境下,dispatch_once 也可以保证线程安全。

/*** 一次性代码(只执行一次)dispatch_once*/
- (void)once {static dispatch_once_t onceToken;dispatch_once(&onceToken, ^{// 只执行 1 次的代码(这里面默认是线程安全的)});
}

6.dispatch_apply(快速迭代方法)

dispatch_apply 按照指定的次数将指定的任务追加到指定的队列中,并等待全部队列执行结束。
如果是在串行队列中使用 dispatch_apply,那么就和 for 循环一样,按顺序同步执行。但是这样就体现不出快速迭代的意义了。
我们可以利用并发队列进行异步执行。比如说遍历 0~5 这 6 个数字,for 循环的做法是每次取出一个元素,逐个遍历。dispatch_apply 可以 在多个线程中同时(异步)遍历多个数字。
还有一点,无论是在串行队列,还是并发队列中,dispatch_apply 都会等待全部任务执行完毕,这点就像是同步操作,也像是队列组中的 dispatch_group_wait方法。

/*** 快速迭代方法 dispatch_apply*/
- (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");
}

在这里插入图片描述
因为是在并发队列中异步执行任务,所以各个任务的执行时间长短不定,最后结束顺序也不定。但是 apply—end 一定在最后执行。这是因为 dispatch_apply 方法会等待全部任务执行完毕。

7.dispatch_suspend & dispatch_resume(暂停和恢复)

dispatch_suspend函数可以随时暂停某个队列,等需要执行的时候使用dispatch_resume恢复即可。

 dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);dispatch_suspend(queue);dispatch_resume(queue);

8.dispatch_semaphore(信号量)

GCD 中的信号量是指 Dispatch Semaphore,是持有计数的信号。类似于过高速路收费站的栏杆。可以通过时,打开栏杆,不可以通过时,关闭栏杆。在 Dispatch Semaphore 中,使用计数来完成这个功能,计数小于 0 时需要等待,不可通过。计数为 0 或大于 0 时,不用等待可通过。计数大于 0 且计数减 1 时不用等待,可通过。
Dispatch Semaphore 提供了三个方法:

  • dispatch_semaphore_create:创建一个 Semaphore 并初始化信号的总量
  • dispatch_semaphore_signal:发送一个信号,让信号总量加 1
  • dispatch_semaphore_wait:可以使总信号量减 1,信号总量小于 0
    时就会一直等待(阻塞所在线程),否则就可以正常执行。

注意:信号量的使用前提是:想清楚你需要处理哪个线程等待(阻塞),又要哪个线程继续执行,然后使用信号量。

Dispatch Semaphore 在实际开发中主要用于:

  • 保持线程同步,将异步执行任务转换为同步执行任务
  • 保证线程安全,为线程加锁

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

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

相关文章

Android制作.9图

需求背景&#xff1a;android 启动图变形 开发语言&#xff1a;uni-app&#xff0c;uni-app官网 俗语曰&#xff1a;授人以鱼不如授人以渔 原创地址&#xff1a;Android制作.9图 语雀 一.工具 使用android studio&#xff0c;因为android studio已经集成.9.png制作工具&a…

某勾求职网逆向分析

搜索目标: aHR0cHM6Ly93d3cubGFnb3UuY29tL3duL2pvYnM/cG49MSZweD1kZWZhdWx0JmZyb21TZWFyY2g9dHJ1ZSZrZD0lRTYlOTUlQjAlRTYlOEQlQUUlRTUlODglODYlRTYlOUUlOTA= 抓包分析 请求和返回都是加密的 请求头部也有未知参数 跟栈分析 请求和返回是一个AES加密,加密的KEY是session s…

鸿蒙OS开发:典型页面场景【一次开发,多端部署】(信息应用)案例

信息应用 简介 内容介绍 Mms应用是OpenHarmony中预置的系统应用&#xff0c;主要的功能包含信息查看、发送短信、接收短信、短信送达报告、删除短信等功能。 架构图 目录 /Mms/ ├── doc # 资料 ├── entry │ └── src │…

springboot3项目练习详细步骤(第四部分:文件上传、登录优化、多环境开发)

目录 本地文件上传 接口文档 业务实现 登录优化 SpringBoot集成redis 实现令牌主动失效机制 多环境开发 本地文件上传 接口文档 业务实现 创建FileUploadController类并编写请求方法 RestController public class FileUploadController {PostMapping("/upload&…

Flink 通过 paimon 关联维表,内存降为原来的1/4

你好&#xff0c;我是 shengjk1&#xff0c;多年大厂经验&#xff0c;努力构建 通俗易懂的、好玩的编程语言教程。 欢迎关注&#xff01;你会有如下收益&#xff1a; 了解大厂经验拥有和大厂相匹配的技术等 希望看什么&#xff0c;评论或者私信告诉我&#xff01; 文章目录 一…

力扣62 不同路径 Java版本

文章目录 题目描述代码 题目描述 一个机器人位于一个 m x n 网格的左上角 &#xff08;起始点在下图中标记为 “Start” &#xff09;。 机器人每次只能向下或者向右移动一步。机器人试图达到网格的右下角&#xff08;在下图中标记为 “Finish” &#xff09;。 问总共有多少…

C++笔试强训day35

目录 1.奇数位丢弃 2.求和 3.计算字符串的编辑距离 1.奇数位丢弃 链接https://www.nowcoder.com/practice/196141ecd6eb401da3111748d30e9141?tpId128&tqId33775&ru/exam/oj 数据量不大&#xff0c;可以直接进行模拟&#xff1a; #include <iostream> #incl…

06_知识点总结(JS高级)

一、进程与线程 1. 进程(process)&#xff1a;程序的一次执行, 它占有一片独有的内存空间 2. 线程(thread)&#xff1a; 是进程内的一个独立执行单元&#xff0c;CPU的基本调度单元, 是程序执行的一个完整流程 3. 进程与线程 * 应用程序必须运行在某个进程的某个线程上 * 一个…

曲线拟合工具软件(免费)

曲线拟合是数据处理中经常用到的数值方法,本质是使用某一个模型(方程或者方程组)将一系列离散的数据拟合成平滑的曲线或者曲面,数值求解出对应的函数参数,大家可以利用MATLAB的曲线拟合工具箱也可以使用第三方的拟合软件,今天我们介绍Welsim免费的曲线拟合软件 1、MATLA…

手撕C语言题典——返回倒数第 k 个节点(面试题)

前言 依旧力扣&#xff0c;这道题之前有做过类似的题&#xff0c;今天给一个新的思路去做&#xff0c;应对面试时候遇到的奇奇怪怪的问题 面试题 02.02. 返回倒数第 k 个节点 - 力扣&#xff08;LeetCode&#xff09;https://leetcode.cn/problems/kth-node-from-end-of-list-…

动手学深度学习23 LeNet

动手学深度学习23 LeNet 1. LeNet2. 代码3. QA 1. LeNet 两层卷积两层池化两层全连接 卷积就是让每一层shape不断压缩变小【高宽减少】&#xff0c;通道数增多&#xff0c;把特征信息放到不同的通道里面。每一个通道认为是一个模式。然后再做全连接的输入。 2. 代码 impor…

css3 笔记02

目录 01 过渡 02 rotate旋转 03 translate函数 04 真正的3D 05 动画 06 阴影 07 自定义字体库 08 自定义动画库 01 过渡 过渡属性的使用: transition-property:要过渡的css属性名 多个属性用逗号隔开 过渡所有属性就写all transition-duration: 过渡的持续时间 s秒 …

vue实现加入购物车动效

实现 实现逻辑&#xff1a; 点击添加购物车按钮时&#xff0c;获取当前点击位置event的clientX 、clientY&#xff1b;动态创建移动的小球&#xff0c;动态计算小球需要移动到的位置&#xff08;通过ref 的getBoundingClientRect获取统计元素按钮位置&#xff09;&#xff1b…

985上交应届生转正12天,被某东辞退了!

&#x1f447;我的小册 45章教程:(小白零基础用Python量化股票分析小册) ,原价299&#xff0c;限时特价2杯咖啡&#xff0c;满100人涨10元。 01.事情起源 最近粉丝群都在转发一个截图&#xff0c;某应届毕业生在某东实习一年&#xff0c;才转正才12天&#xff0c;就因为自己调侃…

如何在Spring Boot中整合PageHelper实现分页功能

1.前言 在开发web应用程序时&#xff0c;经常会遇到需要对数据库中的数据进行分页查询的情况。为了简化分页查询的实现过程&#xff0c;我们可以利用PageHelper这个优秀的分页插件来实现分页功能。本文将介绍如何在Spring Boot项目中整合PageHelper&#xff0c;并演示如何使用它…

生成验证码的奥秘:从列表到字符串的魔法转换

新书上架~&#x1f447;全国包邮奥~ python实用小工具开发教程http://pythontoolsteach.com/3 欢迎关注我&#x1f446;&#xff0c;收藏下次不迷路┗|&#xff40;O′|┛ 嗷~~ 目录 一、引言&#xff1a;验证码生成的背景与需求 二、生成验证码的方法一&#xff1a;列表生成…

2024年zoom会议受主持人账户限制影响,无法加入会议。(错误代码13215)

问题一、老师&#xff0c;你好!我的zoom账户&#xff0c;刚开始注册后可以登录&#xff0c;但是现在登录不了了。代码1044。其次&#xff0c;我如果通过网页版设置会议号&#xff0c;别人也加入不了。代码13215。 这两个问题一般会同时出现。登录失败。(错误代码:1044)一般是创…

Python代码:十八、生成数字列表

1、描述 牛牛在牛客网系统录入了一连串数字&#xff0c;数字之间依靠逗号隔开&#xff0c;你能帮助他将这些数字存储在列表中吗&#xff0c;列表元素以int的形式。 输入描述&#xff1a; 输入一行整数&#xff0c;数字之间以空格间隔。 输出描述&#xff1a; 输出这些数字…

超级初始网络

目录 一、网络发展史 1、独立模式 2、局域网 LAN&#xff08;Local Area Network&#xff09; 3、广域网 WAN (Wide Area Network) 二、网络通信基础 1、IP地址&#xff1a;用于定位主机的网络地址 2、端口号&#xff1a;用于定位主机中的进程 3、网络协议 4、五元组 …

Android 13 高通设备热点低功耗模式

需求: Android设备开启热点,使Iphone设备连接,自动开启低数据模式 低数据模式: 低数据模式是一种在移动网络或Wi-Fi环境下,通过限制应用程序的数据使用、降低数据传输速率或禁用某些后台操作来减少数据流量消耗的优化模式。 这种模式主要用于节省数据流量费用,特别是…