CoderMrWu

生活诚可期,爱情价更高!

IOS多线程

在IOS开发中,实现多线程的方式有多种: NSThread、NSOperation、GCD等

一、NSThread类实现多线程

#import "FZThread.h"

@implementation FZThread

+(void)initWithThread:(SubThread)subthread{
    
    // 新建一个子线程:方法一
    NSThread * t2 = [[NSThread alloc] initWithTarget:self selector:@selector(subThread) object:nil];
    
    t2.name = @"t2";
    
    [t2 start];
    
    
    // 新建一个子线程:方法二
    NSThread * t1 = [[NSThread alloc] initWithBlock:^{
        subthread();
    }];
    // 设置子线程的名称
    t1.name = @"t1";
    // 线程开始执行
    [t1 start];
}

+(void)subThread{
    // 子线程执行代码
    NSLog(@"%@",[NSThread currentThread]);
}

@end

以上是使用 NSThread 类开始两个子线程的代码

NSThread 类的常用属性:

  • name : 为子线程添加名字
  • currentThread : 获取当前线程的信息
  • mainThread : 获取主线程的相关信息
  • threadPriority :子线程的优先级
  • isMainThread : 判断当前是否是主线程
  • finished : 判断线程是否执行完成
  • cancelled : 判读线程是否取消

NSTread 类常用的方法:

  • – (void)start; 线程开始的方法
  • – (void)cancel; 取消线程的方法
  • + (BOOL)isMultiThreaded; : 判断应用程序是否是多线程的
  • + (void)sleepUntilDate:(NSDate *)date; 在指定时间之前将线程休眠
  • + (void)sleepForTimeInterval:(NSTimeInterval)ti; 线程休眠指定时间
  • + (void)exit; 手动结束线程
  • + (double)threadPriority; 获取当前线程的优先级
  • + (BOOL)setThreadPriority:(double)p; 设置当前线程的优先级

二、NSOperation

简介

由于NSOperation该类是抽象类,因此您不直接使用它,而是使用子类或使用系统定义的子类之一(或)执行实际任务。我们可以选择自定义子类或许选择系统提供的子类: NSInvocationOperationNSBlockOperation

示例代码:

    NSBlockOperation * operation = [NSBlockOperation blockOperationWithBlock:^{
        // 子线程中执行的代码
        NSLog(@"%@",[NSThread currentThread]);
    }];
    // 开启线程
    [operation start];

NSOperation 对象是单发对象,它只执行一次任务,结束后就不能再次执行它。所有我们通常把它队列(NSOperationQueue)一起配合使用。队列会自动执行队列中的线程,如果你不想使用队列,你可以调用start方法,手动开启线程。

示例代码:

+(void)initWithOperation{
    // 线程1
    NSBlockOperation * operation1 = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"%@",[NSThread currentThread]);
    }];
    // 线程2
    NSBlockOperation * operation2 = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"%@",[NSThread currentThread]);
    }];
    // 线程队列
    NSOperationQueue * queue = [[NSOperationQueue alloc] init];
    
    [queue addOperation:operation1];
    
    [queue addOperation:operation2];
}

操作依赖

什么是操作依赖呢?

操作依赖是一个线程的执行依赖另一个线程。

示例代码:

+(void)initWithOperation{
    
    __block int num1,num2,sum;
    
    // 线程1
    NSBlockOperation * operation1 = [NSBlockOperation blockOperationWithBlock:^{
        
        num1 = 10;
        
    }];
    // 线程2
    NSBlockOperation * operation2 = [NSBlockOperation blockOperationWithBlock:^{
        num2 = 20;
    }];
    
    // 线程2
    NSBlockOperation * operation3 = [NSBlockOperation blockOperationWithBlock:^{
        sum = num2 + num1;
        NSLog(@"%d",sum);
    }];
    
    // 添加依赖
    [operation3 addDependency:operation1];
    
    [operation3 addDependency:operation2];
    
    // 线程队列
    NSOperationQueue * queue = [[NSOperationQueue alloc] init];
    
    [queue addOperation:operation3];
    
    [queue addOperation:operation1];
    
    [queue addOperation:operation2];
    
}

添加依赖时,得出sum的值为 30 ,不添加依赖,sum的值不一定。

NSOperation 相关属性:

  • isCancelled : 线程是否取消
  • isAsynchronous : 线程是否是异步的
  • isExecuting : 线程是否正在执行
  • isFinished : 线程是否执行完毕
  • isReady : 线程是否准备完毕
  • dependencies : 获取线程的依赖
  • queuePriority : 队列优先级
  • completionBlock : 线程执行完成调用的Block

NSOperation 相关方法:

  • -(void)start; 开始执行操作
  • -(void)main; 执行接收者的非并行任务。
  • -(void)cancel; 通知操作对象它应该停止执行其任务
  • – (void) addDependency; 添加依赖
  • – (void) removeDependency; 删除依赖

三、GCD

一、简介

GCD的实现C语言的API ,GCD中有任务和队列两个概念。

任务:就是执行操作的意思,换句话说就是你在线程中执行的那段代码。在 GCD 中是放在 block 中的。
执行任务有两种方式:『同步执行』 和 『异步执行』。两者的主要区别是:是否等待队列的任务执行结束,以及是否具备开启新线程的能力

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

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

在 GCD 中有两种队列:『串行队列』 和 『并发队列』。两者都符合 FIFO(先进先出)的原则。两者的主要区别是:执行顺序不同,以及开启线程数不同。

串行队列(Serial Dispatch Queue)
每次只有一个任务被执行。让任务一个接着一个地执行。(只开启一个线程,一个任务执行完毕后,再执行下一个任务)

并发队列(Concurrent Dispatch Queue)
可以让多个任务并发(同时)执行。(可以开启多个线程,并且同时执行任务)

二、创建队列

1、创建队列
创建队列使用dispatch_queue_create函数
第一个参数是队列名称,可以为空
第二个参数是队列类型:
DISPATCH_QUEUE_SERIAL 表示串行队列,DISPATCH_QUEUE_CONCURRENT 表示并发队列。

示例代码:

    // 创建串行队列
    dispatch_queue_t queue = dispatch_queue_create(NULL, DISPATCH_QUEUE_SERIAL);
    
    // 创建并行队列
    dispatch_queue_t queue2 = dispatch_queue_create(NULL, DISPATCH_QUEUE_CONCURRENT);
    

对于串行队列,GCD还提供了主队列(Main Dispatch Queue),主队列中的所有任务都放在主线程中执行

    // 获取主队列
    dispatch_queue_t mainQueue = dispatch_get_main_queue();

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

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

三、创建任务

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

     // 获取主队列
     dispatch_queue_t mainQueue = dispatch_get_main_queue();
     
    // 创建同步任务
    dispatch_sync(mainQueue, ^{
        // 子线程中执行的代码
        NSLog(@"%@",[NSThread currentThread]);
    });
    

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

    // 创建异步任务
    dispatch_async(globalQueue, ^{
        // 子线程中执行的代码
        NSLog(@"%@",[NSThread currentThread]);
    });

四、任务和队列之间不同的组合

区别 并发队列 串行队列 主队列
同步(sync) 没有开启新线程,串行执行任务 没有开启新线程,串行执行 死锁,卡住不执行
异步(async) 有开启新线程,并发执行任务 有开启新线程(1条),串行执行任务 没有开启新线程,串行执行任务

五、GCD的基本使用

1、同步执行 + 并发队列

/**
 * 同步执行 + 并发队列
 * 特点:在当前线程中执行任务,不会开启新线程,执行完一个任务,再执行下一个任务。
 */
- (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");
}

2、异步执行 + 并发队列

/**
 * 异步执行 + 并发队列
 * 特点:可以开启多个线程,任务交替(同时)执行。
 */
- (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");
}

3、同步执行 + 串行队列
不会开启新线程,在当前线程执行任务。任务是串行的,执行完一个任务,再执行下一个任务。

/**
 * 同步执行 + 串行队列
 * 特点:不会开启新线程,在当前线程执行任务。任务是串行的,执行完一个任务,再执行下一个任务。
 */
- (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");
}

4、异步执行 + 串行队列

/**
 * 异步执行 + 串行队列
 * 特点:会开启新线程,但是因为任务是串行的,执行完一个任务,再执行下一个任务。
 */
- (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");
}

5、同步执行 + 主队列
同步执行 + 主队列 在不同线程中调用结果也是不一样,在主线程中调用会发生死锁问题,而在其他线程中调用则不会卡住,也不会开启新线程,执行完一个任务,再执行下一个任务。

6、异步执行 + 主队列
只在主线程中执行任务,执行完一个任务,再执行下一个任务

/**
 * 异步执行 + 主队列
 * 特点:只在主线程中执行任务,执行完一个任务,再执行下一个任务
 */
- (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");
}

六、GCD的其他方法

1、GCD 栅栏方法: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]);      // 打印当前线程
    });
}

2、GCD 延时执行方法: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]);  // 打印当前线程
    });
}

3、GCD 一次性代码(只执行一次):dispatch_once

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

4、GCD 快速迭代方法:dispatch_apply
使用不同的线程进行遍历

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

七、GCD 队列组:dispatch_group

1、dispatch_group_notify
监听 group 中任务的完成状态,当所有的任务都执行完成后,追加任务到 group 中,并执行任务。

+(void)groupNotify{
    
    // 首先会打印
    NSLog(@"%@",[NSThread currentThread]);
    
    dispatch_group_t group = dispatch_group_create();
    
    // 异步队列: 开启新线程
    dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        // 追加任务1
        [NSThread sleepForTimeInterval:2];
        // 线程休眠两秒后打印
        NSLog(@"%@",[NSThread currentThread]);
    });
    
    // 异步队列: 开启新线程
    dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        // 追加任务2
        [NSThread sleepForTimeInterval:4];
        // 线程休眠4秒后打印
        NSLog(@"%@",[NSThread currentThread]);
    });
    
    dispatch_group_notify(group, dispatch_get_main_queue(), ^{
        // 前面的任务执行完毕之后,才会执行下面的任务
        [NSThread sleepForTimeInterval:3];
        NSLog(@"%@",[NSThread currentThread]);
    });
    
}

2、dispatch_group_wait
暂停当前线程(阻塞当前线程),等待指定的 group 中的任务执行完成后,才会往下继续执行。

+(void)groupNotify{
    
    // 首先会打印
    NSLog(@"%@",[NSThread currentThread]);
    
    dispatch_group_t group = dispatch_group_create();
    
    // 异步队列: 开启新线程
    dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        // 追加任务1
        [NSThread sleepForTimeInterval:2];
        // 线程休眠两秒后打印
        NSLog(@"%@",[NSThread currentThread]);
    });
    
    // 异步队列: 开启新线程
    dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        // 追加任务2
        [NSThread sleepForTimeInterval:4];
        // 线程休眠4秒后打印
        NSLog(@"%@",[NSThread currentThread]);
    });
    
    // 等待上面的任务执行完成之后才会继续往下执行
    dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
    
    // 以下任务会等待任务1和任务2执行完成之后才会在执行
    // 异步队列: 开启新线程
    dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        // 追加任务3
         [NSThread sleepForTimeInterval:2];
        NSLog(@"%@",[NSThread currentThread]);
    });
    // 异步队列: 开启新线程
    dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        // 追加任务4
         [NSThread sleepForTimeInterval:2];
        NSLog(@"%@",[NSThread currentThread]);
    });
    
}

3、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 中的任务。

+(void)groupNotify{
    
    // 首先会打印
    NSLog(@"%@",[NSThread currentThread]);
    
    dispatch_group_t group = dispatch_group_create();
    // 执行任务数加一
    dispatch_group_enter(group);
    // 异步队列: 开启新线程
    dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        // 追加任务1
        [NSThread sleepForTimeInterval:2];
        // 线程休眠两秒后打印
        NSLog(@"%@",[NSThread currentThread]);
        // group 执行任务数减一
        dispatch_group_leave(group);
    });
    // 执行任务数加一
    dispatch_group_enter(group);
    // 异步队列: 开启新线程
    dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        // 追加任务2
        [NSThread sleepForTimeInterval:4];
        // 线程休眠4秒后打印
        NSLog(@"%@",[NSThread currentThread]);
        // 执行任务数减一
        dispatch_group_leave(group);
    });
    
    // 只有group任务数等于0的时候,下面的任务才会执行
    dispatch_group_notify(group, dispatch_get_main_queue(), ^{
       //回到主队列
        NSLog(@"%@",[NSThread currentThread]);
    });
    
}

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

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

Dispatch Semaphore 在实际开发中主要用于:
1、保持线程同步,将异步执行任务转换为同步执行任务
2、保证线程安全,为线程加锁

4.1、 Dispatch Semaphore 实现线程同步

+(void)semaphoreSync{
    
    NSLog(@"%@",[NSThread currentThread]);
    
    // 创建信号总量
    dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
    
    __block int num = 0;
    
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        
        // 线程阻塞2秒
        [NSThread sleepForTimeInterval:2];
        
        num = 100;
        
        // 发送一个信号,让信号总量加一
        dispatch_semaphore_signal(semaphore);
        
    });
    // 可以使总信号量减 1,信号总量小于 0 时就会一直等待(阻塞所在线程),否则就可以正常执行。
    dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
    
    NSLog(@"%d",num);
    
}

Dispatch Semaphore 执行步骤:
1、semaphore 初始创建时计数为 0。
2、异步执行 将 任务 1 追加到队列之后,不做等待,接着执行 dispatch_semaphore_wait 方法,semaphore 减 1,此时 semaphore == -1,当前线程进入等待状态。
3、然后,异步任务 1 开始执行。任务 1 执行到 dispatch_semaphore_signal 之后,总信号量加 1,此时 semaphore == 0,正在被阻塞的线程(主线程)恢复继续执行。
4、最后打印 semaphore—end,number = 100。

4.2、Dispatch Semaphore 线程安全和线程同步(为线程加锁)
多个线程访问同一个变量,并执行写的操作,那么就需要对考虑线程安全,需要对写操作加锁。

-(void)writeSum{
    
    _sum = 50;
    
    // 创建信息总量
    _semaphore =  dispatch_semaphore_create(1);
    
    dispatch_queue_t queue1 = dispatch_queue_create("num1", DISPATCH_QUEUE_SERIAL);
    
    dispatch_queue_t queue2 = dispatch_queue_create("num2", DISPATCH_QUEUE_SERIAL);
    // 异步线程1
    dispatch_async(queue1, ^{
        [self numChange];
    });
    // 异步线程2
    dispatch_async(queue2, ^{
        [self numChange];
    });
    
}

#pragma mark- 修改sum的数量
-(void)numChange{

    while (1) {
        // 为线程加锁
        dispatch_semaphore_wait(_semaphore, DISPATCH_TIME_FOREVER);
        if (_sum >0) {
            self.sum --;
            NSLog(@"Thred:%@----sum:%d",[NSThread currentThread],_sum);
        }else{
            NSLog(@"Thred:%@----sum:%d",[NSThread currentThread],_sum);
            // 解锁
            dispatch_semaphore_signal(_semaphore);
            break;
        }
        // 解锁
        dispatch_semaphore_signal(_semaphore);
    }
}

程序的打印结果是按照顺序打印的,没有出现混乱的现象。

点赞