【iOS】GCD学习

文章目录

  • 前言
  • 一、什么是GCD
  • 二、任务和队列
  • 三、GCD基本使用
    • 队列的创建
    • 任务的创建
    • 任务和队列的组合
      • 同步任务+串行队列
      • 异步任务+串行队列
      • 异步任务+并发队列
      • 主队列+同步任务
      • 主队列+异步任务
  • 四、 Main Dispatch Queue & Global Dispatch Queue
  • 五、dispatch_set_target_queue函数
  • 六、dispatch_after
  • 七、Dispatch Group
  • 八、GCD 栅栏方法:dispatch_barrier_async
  • 八、dispatch_sync & dispatch_async
  • 九、dispatch_apply-GCD 快速迭代
  • 十、dispatch_suspend & dispatch_resume
  • 十一、dispatch_semaphore-GCD 信号量
    • Dispatch Semaphore 线程同步
    • Dispatch Semaphore 线程安全和线程同步(为线程加锁)
  • 十二、dispatch_once


前言

本博客撰写一下小蓝书的GCD

一、什么是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中是放在block中的。执行任务有两种方式:同步执行和异步执行。两者的主要区别是:是否具备开启新线程的能力。

同步执行(sync):只能在当前线程中执行任务,不具备开启新线程的能力
异步执行(async):可以在新的线程中执行任务,具备开启新线程的能力

队列
队列是一种特殊的线性表,队列中允许插入操作的一端称为队尾,允许删除操作的一端称为队头,是一种先进先出的结构。在GCD里面队列是指执行任务的等待队列,是用来存放任务的。按照队列的结构特性,新任务总是插入在队列的末尾,而任务的执行总是从队列的对头输出,每读取一个任务,则从队列中释放一个任务。GCD的队列分为串行队列和并发队列两种,两者都符合 FIFO(先进先出)的原则。两者的主要区别是:执行顺序不同,以及开启线程数不同。

串行队列:只开启一个线程,每次只能有一个任务执行,等待执行完毕后才会执行下一个任务。
并发队列:可以让对个任务同时执行,也就是开启多个线程,让多个任务同时执行。

两者之间区别如下图所示:
【iOS】GCD学习_第1张图片
【iOS】GCD学习_第2张图片
【iOS】GCD学习_第3张图片

三、GCD基本使用

GCD的使用很简单,首先创建一个队列,然后向队列中追加任务,系统会根据任务的类型执行任务。

队列的创建

队列的创建很简单,只需要调用dispatch_queue_create方法传入相对应的参数便可。这个方法有两个参数:

  • 第一个参数表示队列的唯一标识,可以传空。
  • 第二个参数用来识别是串行队列还是并发队列。DISPATCH_QUEUE_SERIAL 表示串行队列,DISPATCH_QUEUE_CONCURRENT表示并发队列。
// 创建串行队列
dispatch_queue_t queue = dispatch_queue_create("com.shen.thread.demo", DISPATCH_QUEUE_SERIAL);
// 创建并发队列
dispatch_queue_t queue = dispatch_queue_create("com.shen.thread.demo", DISPATCH_QUEUE_CONCURRENT);

GCD默认提供一种全局并发队列,调用 dispatch_get_global_queue方法来获取全局并发队列。这个方法需要传入两个参数。

第一个参数是一个长整型类型的参数,表示队列优先级,有DISPATCH_QUEUE_PRIORITY_HIGHDISPATCH_QUEUE_PRIORITY_LOWDISPATCH_QUEUE_PRIORITY_BACKGROUNDDISPATCH_QUEUE_PRIORITY_DEFAULT四个选项,一般用 DISPATCH_QUEUE_PRIORITY_DEFAULT
第二个参数暂时没用,用 0 即可。

GCD默认提供了主队列,调用dispatch_get_main_queue方法获取,所有放在主队列中的任务都会在主线程中执行。主队列是一种串行队列

// 获取全局并发队列
dispatch_queue_t globalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
// 主队
dispatch_queue_t mainQueue = dispatch_get_main_queue();

任务的创建

GCD调用dispatch_sync创建同步任务,调用dispatch_async创建异步任务。任务的内容都是在block代码块中。

//异步任务
dispatch_async(queue, ^{
   //异步执行的代码
});
 
//同步任务   
dispatch_sync(queue, ^{
   //同步执行的代码
});

任务和队列的组合

创建的任务需要放在队列中去执行,同时考虑到主队列的特殊性,那么在不考虑嵌套任务的情况下就会存在同步任务+串行队列、同步任务+并发队列、异步任务+串行队列、异步任务+并发队列、主队列+同步任务、主队列+异步任务六种组合,下面我们来分析下这几种组合。

同步任务+串行队列

//同步任务+串行队列
- (void)syncTaskWithSerial {
    NSLog(@"currentThread:%@", [NSThread currentThread]);
    dispatch_queue_t queue = dispatch_queue_create("com.shen.thread.demo", DISPATCH_QUEUE_SERIAL);
    dispatch_sync(queue, ^{
        NSLog(@"currentThread-1:%@", [NSThread currentThread]);
    });
    dispatch_sync(queue, ^{
        NSLog(@"currentThread-2:%@", [NSThread currentThread]);
    });
    dispatch_sync(queue, ^{
        NSLog(@"currentThread-3:%@", [NSThread currentThread]);
    });
}

【iOS】GCD学习_第4张图片
从上面代码运行的结果可以看出,并没有开启新的线程,任务是按顺序执行的。

异步任务+串行队列

//异步任务加串行队列
- (void)asyncTaskWithSeria{
    NSLog(@"current thread:%@", [NSThread currentThread]);
    dispatch_queue_t queue = dispatch_queue_create("com.shen.thread.demo", DISPATCH_QUEUE_SERIAL);
    dispatch_async(queue, ^{
        NSLog(@"current thread-1:%@", [NSThread currentThread]);
    });
    dispatch_async(queue, ^{
        NSLog(@"current thread-2:%@", [NSThread currentThread]);
    });
    dispatch_async(queue, ^{
        NSLog(@"current thread-3:%@", [NSThread currentThread]);
    });
    NSLog(@"4");
}

【iOS】GCD学习_第5张图片
从上面代码运行的结果可以看出,开启了一个新的线程,说明异步任务具备开启新的线程的能力,但是由于任务是在串行队列中执行的,所以任务是顺序执行的

这里需要注意的是4这个输出,这其实意味着我们在我们创建的这个队列中异步执行了三个块,这也意味着这三个块将被提交到队列中,而当前线程会继续执行后面的代码(而4就是后面的代码)。由于队列是串行的,它们将按照它们被提交的顺序依次执行。

异步任务+并发队列

- (void)asyncTaskWithConcurrent{
    NSLog(@"current thread:%@", [NSThread currentThread]);
    dispatch_queue_t queue = dispatch_queue_create("com.shen.thread.demo", DISPATCH_QUEUE_CONCURRENT);
    dispatch_async(queue, ^{
        NSLog(@"current thread-1:%@", [NSThread currentThread]);
    });
    dispatch_async(queue, ^{
        NSLog(@"current thread-2:%@", [NSThread currentThread]);
    });
    dispatch_async(queue, ^{
        NSLog(@"current thread-3:%@", [NSThread currentThread]);
    });
    dispatch_async(queue, ^{
        NSLog(@"current thread-4:%@", [NSThread currentThread]);
    });
}

【iOS】GCD学习_第6张图片
从上面代码的运行结果可以看出,生成了多个线程,并且任务是随机执行(并发执行)的

主队列+同步任务

-(void)syncTaskWithMain{
    NSLog(@"currentThread---%@",[NSThread currentThread]);
    dispatch_queue_t queue = dispatch_get_main_queue();
    dispatch_sync(queue, ^{
        NSLog(@"1---%@",[NSThread currentThread]);
    });
    
    dispatch_sync(queue, ^{
        NSLog(@"2---%@",[NSThread currentThread]);
    });
    
    dispatch_sync(queue, ^{
        NSLog(@"3---%@",[NSThread currentThread]);
    });
    
    NSLog(@"4");
}

在这里插入图片描述

这段代码出现了错误,因为我们在主线程中执行syncTaskWithMain方法,相当于把syncTaskWithMain 任务放到了主线程的队列中。而 同步执行等待当前队列中的任务执行完毕,才会接着执行。当我们把任务1追加到主队列中,任务 1 就在等待主线程处理完 syncTaskWithMain 任务。而syncTaskWithMain 任务需要等待任务 1,2,3 执行完毕,这样就形成了相互等待的情况,产生了死锁

主队列+异步任务

-(void)asyncTaskWithMain{
    NSLog(@"currentThread---%@",[NSThread currentThread]);
    dispatch_queue_t queue = dispatch_get_main_queue();
    dispatch_async(queue, ^{
        NSLog(@"1---%@",[NSThread currentThread]);
        NSLog(@"任务1");
    });
    
    dispatch_async(queue, ^{
        NSLog(@"2---%@",[NSThread currentThread]);
        NSLog(@"任务2");
    });
    
    dispatch_async(queue, ^{
        NSLog(@"3---%@",[NSThread currentThread]);
        NSLog(@"任务3");
    });
    
    NSLog(@"4");
}

【iOS】GCD学习_第7张图片
从上面代码的运行结果可以看出,虽然是异步任务,但是并没有开启新的线程,任然是在主线程中执行,并且任务是顺序执行(因为主队列是串行队列)的。

四、 Main Dispatch Queue & Global Dispatch Queue

  • 对于串行队列,GCD 默认提供了:主队列(Main Dispatch Queue
    【iOS】GCD学习_第8张图片
    主队列其实并不特殊。 主队列的实质上就是一个普通的串行队列,只是因为默认情况下,当前代码是放在主队列中的,然后主队列中的代码,有都会放到主线程中去执行,所以才造成了主队列特殊的现象
  • 同时对于并行队列 GCD 默认提供了 全局并发队列(Global Dispatch Queue)
    Global Dispatch Queue 有四个执行优先级。XNU内核管理的线程,将各自使用的Global Dispatch Queue 的执行优先级作为线程的优先级使用。在Global Dispatch Queue 里追加处理的时候应选择与处理内容对应的执行优先级的Global Dispatch Queue
    【iOS】GCD学习_第9张图片
    不同优先级Queue获取方法
    【iOS】GCD学习_第10张图片
// global 高优先级
    dispatch_queue_t globalDispatchQueueHigh = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0);
    
    // 默认优先级
    dispatch_queue_t globalDispatchQueueDefault = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    
    // 低优先级
    dispatch_queue_t globalDispatchQueueLow = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0);
    // 后台优先级
    dispatch_queue_t globalDispatchQueueBackGround = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0);


五、dispatch_set_target_queue函数

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

 dispatch_set_target_queue(serialQueue, globalDispatchQueueBackGround);

第一个参数是需要变更优先级的参数,第二个参数指定了与那个优先级相同的队列。

添加执行层次

【iOS】GCD学习_第11张图片
在必须将不可并行执行的处理追加到多个Serial Dispatch Queue 中时,如果使用dispatch set target queue 西数将目标指定为某 一个Serial DispatchQueue,即可防止处理并行执行。

六、dispatch_after

当我们需要延迟执行一段代码时,就需要用到GCD的dispatch_after方法。

- (void)after {
    NSLog(@"run -- 0");
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(4.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        // 4秒后异步执行这里的代码...
       NSLog(@"run -- 2");
    });
}

在这里插入图片描述

七、Dispatch Group

Dispatch Group 是 Grand Central Dispatch(GCD)中的一种机制,用于追踪一组任务的完成情况。你可以使用 Dispatch Group 来等待一组任务全部完成后执行其他操作。

以下是一个简单的 Objective-C 代码示例,演示如何使用 Dispatch Group:

- (void)performTasksWithDispatchGroup {
    // 创建一个 Dispatch Group
    dispatch_group_t group = dispatch_group_create();
    
    // 获取全局并发队列
    dispatch_queue_t globalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    
    // 在 Dispatch Group 中异步执行任务1
    dispatch_group_async(group, globalQueue, ^{
        NSLog(@"Task 1 started");
        // 模拟耗时任务
        [NSThread sleepForTimeInterval:2];
        NSLog(@"Task 1 completed");
    });
    
    // 在 Dispatch Group 中异步执行任务2
    dispatch_group_async(group, globalQueue, ^{
        NSLog(@"Task 2 started");
        // 模拟耗时任务
        [NSThread sleepForTimeInterval:3];
        NSLog(@"Task 2 completed");
    });
    
    // 在 Dispatch Group 中异步执行任务3
    dispatch_group_async(group, globalQueue, ^{
        NSLog(@"Task 3 started");
        // 模拟耗时任务
        [NSThread sleepForTimeInterval:1];
        NSLog(@"Task 3 completed");
    });
    
    // 等待 Dispatch Group 中的所有任务完成
    dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
    
    // 所有任务完成后执行这里的代码
    NSLog(@"All tasks completed");
    
    // 注意:记得在不需要 Dispatch Group 时释放它
    dispatch_release(group);
}

在上述代码中,我们使用 dispatch_group_create 创建了一个 Dispatch Group,并在其中异步执行了三个任务。然后,通过 dispatch_group_wait 等待 Dispatch Group 中的所有任务完成。最后,当所有任务完成后,输出 “All tasks completed”。

使用队列组的原因

无论向如何类型的Queue追加处理,Dispatch Group都可以监视这些处理直到执行结束,一旦执行结束,就可以将结束处理追加到DispatchQueue中。

八、GCD 栅栏方法:dispatch_barrier_async

dispatch_barrier_async 是 GCD(Grand Central Dispatch)中的一个方法,它用于在并发队列中插入一个“栅栏”(barrier)。栅栏的作用是在其前面的任务执行完毕后,再执行栅栏任务,然后再执行栅栏之后的任务。这可以用于确保在多线程环境中某些操作的顺序性和互斥性。
【iOS】GCD学习_第12张图片

- (void)barrierTask {
    dispatch_queue_t queue = dispatch_queue_create("com.shen.thread.demo", DISPATCH_QUEUE_CONCURRENT);
    NSLog(@"start");
    dispatch_async(queue, ^{
        NSLog(@"currentThread-1:%@", [NSThread currentThread]);
        [NSThread sleepForTimeInterval:2];
    });
    dispatch_barrier_async(queue, ^{
        NSLog(@"currentThread-2:%@", [NSThread currentThread]);
        [NSThread sleepForTimeInterval:2];
    });
    NSLog(@"pause");
    dispatch_async(queue, ^{
        NSLog(@"currentThread-3:%@", [NSThread currentThread]);
    });
    NSLog(@"end");
}

这段代码的输出是这样的:
【iOS】GCD学习_第13张图片
异步任务1(dispatch_async)和栅栏任务2(dispatch_barrier_async)是在同一个并发队列中执行的,而且它们都是异步提交的,意味着它们会在主线程中执行而不会阻塞主线程
因此,在主线程执行到 NSLog(@“pause”) 时,异步任务1和栅栏任务2已经被提交到并发队列中,但它们的执行是异步的,主线程会继续执行后面的代码,而不会等待它们执行完毕。

这也说明了异步任务1要等栅栏任务2结束后才能继续执行异步任务3

八、dispatch_sync & dispatch_async

dispatch_async意味着非同步,就是将制定的block非同步的追加到queue中, dispatch_async不会进行任何等待
【iOS】GCD学习_第14张图片
dispatch_sync意味着同步,就是将制定的block同步的追加到queue中, dispatch_sync会在追加结束之前一直等待# 二、使用步骤
【iOS】GCD学习_第15张图片

九、dispatch_apply-GCD 快速迭代

dispatch_apply 是 Grand Central Dispatch(GCD)中的一个方法,它可以用于在指定的队列上多次执行一个指定的块。dispatch_apply 是一个同步函数,会等待所有任务执行完毕后才会继续执行。他其实就像线程中的for循环

dispatch_apply函数一般多用于并行队列,若是在串行队列使用其实for循环原理相似

void dispatch_apply(size_t iterations, dispatch_queue_t queue, void (^block)(size_t));
  • iterations: 要执行块的次数。
  • queue: 任务提交的队列,可以是串行队列或并发队列。
  • block: 要执行的任务块,接受一个参数,即当前的索引值
/**
 * 快速迭代方法 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");
}

【iOS】GCD学习_第16张图片

需要注意的是,dispatch_apply 是一个同步函数,会等待所有任务完成后才会继续执行后续代码。这也是为什么 “All tasks completed” 的输出会等待所有任务执行完毕后才显示。

十、dispatch_suspend & dispatch_resume

这两个函数可以随时挂起某个队列,等需要执行的时候恢复即可

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

十一、dispatch_semaphore-GCD 信号量

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

这里一定要理解Dispatch Semaphore提供的三个方法

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

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

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

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

Dispatch Semaphore 线程同步

我们在开发中,会遇到这样的需求:异步执行耗时任务,并使用异步执行的结果进行一些额外的操作。换句话说,相当于,将异步执行任务转换为同步执行任务

下面,我们来利用 Dispatch Semaphore 实现线程同步,将异步执行任务转换为同步执行任务。

/**
 * semaphore 线程同步
 */
- (void)semaphoreSync {
    
    NSLog(@"currentThread---%@",[NSThread currentThread]);  // 打印当前线程
    NSLog(@"semaphore---begin");
    
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
    
    __block int number = 0;
    dispatch_async(queue, ^{
        // 追加任务 1
        [NSThread sleepForTimeInterval:2];              // 模拟耗时操作
        NSLog(@"1---%@",[NSThread currentThread]);      // 打印当前线程
        
        number = 100;
        
        dispatch_semaphore_signal(semaphore);
    });
    
    dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
    NSLog(@"semaphore---end,number = %zd",number);
}

【iOS】GCD学习_第17张图片
【iOS】GCD学习_第18张图片

Dispatch Semaphore 线程安全和线程同步(为线程加锁)

线程安全:如果你的代码所在的进程中有多个线程在同时运行,而这些线程可能会同时运行这段代码。如果每次运行结果和单线程运行的结果是一样的,而且其他的变量的值也和预期的是一样的,就是线程安全的。

若每个线程中对全局变量、静态变量只有读操作,而无写操作,一般来说,这个全局变量是线程安全的;若有多个线程同时执行写操作(更改变量),一般都需要考虑线程同步,否则的话就可能影响线程安全。

线程同步:可理解为线程 A 和 线程 B 一块配合,A 执行到一定程度时要依靠线程 B 的某个结果,于是停下来,示意 B 运行;B 依言执行,再将结果给 A;A 再继续操作。

这里就涉及到之前了解过的售票问题,下面,我们模拟火车票售卖的方式,实现 NSThread 线程安全和解决线程同步问题。

场景:总共有 50 张火车票,有两个售卖火车票的窗口,一个是北京火车票售卖窗口,另一个是上海火车票售卖窗口。两个窗口同时售卖火车票,卖完为止。

先来看看不考虑线程安全的代码:

/**
 * 非线程安全:不使用 semaphore
 * 初始化火车票数量、卖票窗口(非线程安全)、并开始卖票
 */
- (void)initTicketStatusNotSave {
    NSLog(@"currentThread---%@",[NSThread currentThread]);  // 打印当前线程
    NSLog(@"semaphore---begin");
    
    self.ticketSurplusCount = 50;
    
    // queue1 代表北京火车票售卖窗口
    dispatch_queue_t queue1 = dispatch_queue_create("net.bujige.testQueue1", DISPATCH_QUEUE_SERIAL);
    // queue2 代表上海火车票售卖窗口
    dispatch_queue_t queue2 = dispatch_queue_create("net.bujige.testQueue2", DISPATCH_QUEUE_SERIAL);
    
    __weak typeof(self) weakSelf = self;
    dispatch_async(queue1, ^{
        [weakSelf saleTicketNotSafe];
    });
    
    dispatch_async(queue2, ^{
        [weakSelf saleTicketNotSafe];
    });
}

/**
 * 售卖火车票(非线程安全)
 */
- (void)saleTicketNotSafe {
    while (1) {
        
        if (self.ticketSurplusCount > 0) {  // 如果还有票,继续售卖
            self.ticketSurplusCount--;
            NSLog(@"%@", [NSString stringWithFormat:@"剩余票数:%d 窗口:%@", self.ticketSurplusCount, [NSThread currentThread]]);
            [NSThread sleepForTimeInterval:0.2];
        } else { // 如果已卖完,关闭售票窗口
            NSLog(@"所有火车票均已售完");
            break;
        }
        
    }
}

【iOS】GCD学习_第19张图片
可以看到在不考虑线程安全,不使用 semaphore 的情况下,得到票数是错乱的,这样显然不符合我们的需求,所以我们需要考虑线程安全问题。

当我们使用信号量对线程进行加锁时

/**
 * 售卖火车票(线程安全)
 */
- (void)saleTicketSafe {
    while (1) {
        // 相当于加锁
        dispatch_semaphore_t semaphoreLock = dispatch_semaphore_create(1);        dispatch_semaphore_wait(semaphoreLock, DISPATCH_TIME_FOREVER);
        
        if (self.ticketSurplusCount > 0) {  // 如果还有票,继续售卖
            self.ticketSurplusCount--;
            NSLog(@"%@", [NSString stringWithFormat:@"剩余票数:%ld 窗口:%@", (long)self.ticketSurplusCount, [NSThread currentThread]]);
            [NSThread sleepForTimeInterval:0.2];
        } else { // 如果已卖完,关闭售票窗口
            NSLog(@"所有火车票均已售完");
            
            // 相当于解锁
            dispatch_semaphore_signal(semaphoreLock);
            break;
        }
        
        // 相当于解锁
        dispatch_semaphore_signal(semaphoreLock);
    }
}

这就保证了我们的线程一次只能处理一个任务,也就使我们的进程由并发队列+异步任务转换为了并发队列+同步任务

十二、dispatch_once

dispatch_once 是 Grand Central Dispatch(GCD)中的一个函数,用于确保某个代码块在程序运行过程中只被执行一次。它提供了一种线程安全的单例模式实现方式,可以防止多个线程同时执行初始化代码。

如此创建的单例模式只会被初始化一次

先声明静态全局变量,只能在当前文件访问 ,单例对象的条件
#import "Person.h"
static id instance;
static id SingleEg;
// 懒汉
@implementation Person
+(id) instance {
    static dispatch_once_t o;
    dispatch_once(&o, ^{
        if (instance == nil) {
            instance = [[super alloc] init];
        }
    });
    return  instance;
}
// 饿汉
+ (id)_singleEg {
    static dispatch_once_t o;
    dispatch_once(&o, ^{
            SingleEg = [[super alloc] init];
    });
    return  SingleEg;
}

这样就保证即使在多线程环境下执行,单例也只会被初始化一次

你可能感兴趣的:(ios,学习)