【iOS】——GCD再学习

news2024/9/21 7:06:05

文章目录

  • 一、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.coloradmin.cn/o/1707153.html

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

相关文章

大模型实战-动手实现单agent

文章目录 入口cli_main.py工具tools.pyprompt prompt_cn.pyLLM 推理 model_provider.py致谢 agent 的核心思想&#xff1a;不断调用 LLM&#xff08;多轮对话&#xff09;&#xff0c;让 LLM 按照指定的格式&#xff08;例如 json&#xff09;进行回复&#xff0c;提取 LLM 回复…

vue 区分多环境打包

需求&#xff1a;区分不同的环境&#xff08;测试、正式环境&#xff09;&#xff0c;接口文档地址不同&#xff1b; 配置步骤&#xff1a; 1、在根目录下面新建 .env.xxx 文件&#xff08;xxx 根据环境不同配置&#xff09; 文件中一定要配置的参数项为&#xff1a;NODE_ENV…

自适应感兴趣区域的级联多尺度残差注意力CNN用于自动脑肿瘤分割| 文献速递-深度学习肿瘤自动分割

Title 题目 Cascade multiscale residual attention CNNs with adaptive ROI for automatic brain tumor segmentation 自适应感兴趣区域的级联多尺度残差注意力CNN用于自动脑肿瘤分割 01 文献速递介绍 脑肿瘤是大脑细胞异常和不受控制的增长&#xff0c;被认为是神经系统…

前端API: IntersectionObserver的那一二三件事

IntersectionObserver 基础 IntersectionObserver 可以监听一个元素和可视区域相交部分的比例&#xff0c;然后在可视比例达到某个阈值的时候触发回调。比如可以用来处理图片的懒加载等等 首先我们来看下基本的格式&#xff1a; const observer new IntersectionObserver(c…

vue 笔记02

目录 01 事件修饰符 02 按键修饰符 03 v-bind属性 04 vue-axios的基本使用 05 vue的生命周期 06 vue生命周期涉及到的其他的知识点 01 事件修饰符 vue的事件修饰符 事件名称.修饰符1.修饰符2...事件驱动函数 stop 阻止冒泡修饰符 prevent 阻止默认行为 once 当前事件只触…

牛客题霸-SQL大厂面试真题(一)

本文基于前段时间学习总结的 MySQL 相关的查询语法&#xff0c;在牛客网找了相应的 MySQL 题目进行练习&#xff0c;以便加强对于 MySQL 查询语法的理解和应用。 由于涉及到的数据库表较多&#xff0c;因此本文不再展示&#xff0c;只提供 MySQL 代码与示例输出。 以下内容是…

期权方向性交易策略怎么制定?

今天期权懂带你了解期权方向性交易策略怎么制定&#xff1f;国内的期权品种已经多达十几种&#xff0c;其中ETF期权是流量最大的品种&#xff0c;截止今日已经上市了十二种ETF期权。 期权方向性交易策略怎么制定&#xff1f; 期权方向性交易策略主要依赖于投资者对市场未来走势…

作业job——kettle开发30

一、作业 大多数ETL项目都需要完成各种各样的维护工作。 例如&#xff0c;如何传送文件;验证数据库表是否存在&#xff0c;等等。而这些操作都是按照一定顺序完成。因为转换以并行方式执行&#xff0c;就需要一个可以串行执行的作业来处理这些操作。 一个作业包含一个或多个作…

WHLUG活动回顾 | 4大技术分享!干货满满,热闹非凡!

内容来源&#xff1a;deepin&#xff08;深度&#xff09;社区 2024 年 5 月 25 日下午&#xff0c;由 deepin&#xff08;深度&#xff09;社区华中科技大学开放原子开源俱乐部联合举办的武汉 Linux 爱好者线下沙龙活动&#xff08;WHLUG&#xff09;在华中科技大学成功举办。…

vue3中的toRaw API

文章目录 什么是toRaw API&#xff1f;为什么需要toRaw&#xff1f;如何使用toRaw&#xff1f;实际应用场景 这两天在写项目的时候&#xff0c;发现了一个之前没用过的api&#xff0c;于是上网查了一下&#xff0c;发现这个api还是挺常用&#xff0c;所以在这记录一下 什么是t…

Android11 事件分发流程

在Android 11 输入系统之InputDispatcher和应用窗口建立联系一文中介绍到&#xff0c;当InputDispatcher写入数据后&#xff0c;客户端这边就会调用handleEvent方法接收数据 //frameworks\base\core\jni\android_view_InputEventReceiver.cpp int NativeInputEventReceiver::h…

springboot项目war包部署到腾讯云服务器

一、购买服务器 试用 1 个月&#xff08;需要实名和人脸验证&#xff09; 云产品免费体验馆_云产品免费试用_个人云产品试用-腾讯云 重置密码 登录以后 二、云服务器安装MySql 登录后&#xff0c;接下来的一切我们使用linux命令来操作。 1、卸载centos默认安装的mariadb rp…

axios和ts的简单使用

按照官网的使用案例简单记下笔记 1&#xff1a;安装 npm install axios 2&#xff1a;案例 一个简单的config配置信息 // 发起一个post请求 axios({method: post,url: /user/12345,data: {firstName: Fred,lastName: Flintstone} }); case // 在 node.js 用GET请求获取…

有哪些藏文翻译器在线翻译?工具分享

有哪些藏文翻译器在线翻译&#xff1f;随着全球化的推进&#xff0c;语言之间的交流变得越来越重要。藏语作为中华民族的重要语言之一&#xff0c;其翻译需求也日益增加。为了满足这一需求&#xff0c;市场上涌现出了多款藏文翻译器在线翻译工具&#xff0c;它们以其高效、准确…

Qt for android : libusb在android中使用

简介 如何在Qt for Android中使用libusb&#xff0c; 其实libusb的文档里面都写的很清楚&#xff0c; 这里只是稍微做下整理。 libusb libusb github源码 libusb release的版本, 有编译好的静态 步骤 1. 下载libusb libusb v1.0.027 源码包 2. 整理提取libusb android使用源…

怎么使用Stable diffusion中的models

Stable diffusion中的models Stable diffusion model也可以叫做checkpoint model&#xff0c;是预先训练好的Stable diffusion权重&#xff0c;用于生成特定风格的图像。模型生成的图像类型取决于训练图像。 如果训练数据中从未出现过猫的图像&#xff0c;模型就无法生成猫的…

【MySQL数据库】 MySQL主从复制

MySQL主从复制 MySQL主从复制主从复制与读写分离的意义主从数据库实现同步&#xff08;主从复制&#xff09;三台mysql服务器搭建主从复制&#xff0c;要求不可以用root帐号同步&#xff0c;要求第三台服务器在测试过1、2的主从复制之后进行主从复制配置 MySQL主从复制 主从复…

FastAPI - 组织模块2

FastAPI没有强制指定某种格式来组织项目结构&#xff0c;开发者可以根据自己喜好和项目需要来定制自己的项目结构。 https://fastapi.tiangolo.com/zh/tutorial/bigger-applications/ 在项目根目录创建python包routers&#xff0c;然后创建member.py文件 member.py文件内容 …

嘴尚绝卤味:健康美味新选择,开启味蕾新旅程!

在这个美食文化繁荣的时代&#xff0c;卤味作为传统小吃界的一颗璀璨明珠&#xff0c;一直深受大众的喜爱。而今天&#xff0c;我要向大家介绍一款不仅美味可口&#xff0c;更注重健康营养的卤味品牌——嘴尚绝卤味。它以其独特的制作工艺和丰富的口感&#xff0c;成为众多卤味…

滚珠花键在工业自动化领域中有什么优势?

滚珠花键是工业自动化设备中重要的传动系统之一&#xff0c;不仅在工业自动化系统中有着广泛的运用&#xff0c;还在机械制造领域、航空航天领域、工业汽车领域、工业机器人、高速铁路、新能源领域 等都得到广泛应用。由于具有高精度、高承载、耐磨损、传递扭矩大等特点&#x…