美文网首页
IOS基础:多线程(下)

IOS基础:多线程(下)

作者: 时光啊混蛋_97boy | 来源:发表于2020-10-23 10:24 被阅读0次

原创:知识点总结性文章
创作不易,请珍惜,之后会持续更新,不断完善
个人比较喜欢做笔记和写总结,毕竟好记性不如烂笔头哈哈,这些文章记录了我的IOS成长历程,希望能与大家一起进步
温馨提示:由于简书不支持目录跳转,大家可通过command + F 输入目录标题后迅速寻找到你所需要的内容

目录

  • 简介
    • 1、CPU是什么
    • 2、操作系统中进程与线程的区别
    • 3、进程间通信
    • 4、线程间通信
    • 5、线程的状态与生命周期
    • 6、主线程
    • 7、线程池
    • 8、多线程同步
    • 9、选择 NSThread、 NSOperation 还是 GCD
  • 一、NSThread
    • 1、pThread的使用
    • 2、NSThread的属性和方法
    • 3、NSThread创建线程
    • 4、NSThread取消和结束线程
  • 二、GCD(Grand Central Dispatch)
    • 1、任务
    • 2、队列(Dispatch Queue)
    • 3、创建队列(串行队列/并发队列)
    • 4、任务执行方式(同步执行/异步执行)
    • 5、组合方式
    • 6、GCD 线程间通讯
    • 7、GCD 栅栏方法
    • 8、GCD 延时执行方法
    • 9、GCD 一次性代码(只执行一次)
    • 10、GCD 快速迭代方法
    • 11、GCD 的队列组
    • 12、GCD 信号量
    • 13、GCD中的定时器
    • 14、GCD底层实现原理
  • 三、NSOperation
    • 1、NSOperation简介
    • 2、NSOperation的创建
    • 3、创建队列 NSOperationQueue
    • 4、将操作添加到队列
    • 5、运用最大并发数实现串行
    • 6、取消或者暂停操作
    • 7、NSOperation 操作依赖
    • 8、NSOperation、NSOperationQueue 线程间的通信
  • 四、多线程与锁
    • 问题
    • 方案一:OSSpinLock自旋锁
    • 方案二:os_unfair_lock互斥锁
    • 方案三:pthread_mutex互斥锁
    • 方案四:pthread_mutex递归锁
    • 方案五:pthread_mutex条件锁
    • 方案六:NSLock锁
    • 方案七:NSRecursiveLock锁
    • 方案八:NSCondition条件锁
    • 方案九:NSConditionLock
    • 方案十:dispatch_semaphore信号量
    • 方案十一:@synchronized
    • 方案十二:atomic
    • 方案十三:pthread_rwlock读写锁
    • 方案十四:dispatch_barrier_async异步栅栏
    • 方案十五:dispatch_group_t调度组
  • Demo
  • 参考文献

续文见上篇 IOS基础:OC的基础语法大杂烩(上)

三、NSOperation

1、NSOperation简介

NSOperation是基于GCD之上的更高一层封装,NSOperation需要配合NSOperationQueue来实现多线程,完全面向对象。但是比GCD更简单易用、代码可读性也更高。

操作(Operation)常用属性和方法
  • 执行操作的意思,换句话说就是你在线程中执行的那段代码。
  • GCD 中是放在block中的。在NSOperation 中,我们使用 NSOperation 子类 NSInvocationOperationNSBlockOperation,或者自定义子类来封装操作。
  • NSOperation必须加入NSOperationQueue才会并发执行,如果直接调用start方法,NSOperation会在当前线程执行。
  • NSOperation必须维护自己的NSOperationQueue,系统只提供对应于主线程的mainQueue,不提供其他形式的队列。
  • 所有开发者创建的NSOperationQueue都是并发队列,队列里的任务采用FIFO,但是是并发执行,不能保证任务执行完成的顺序。
  • 继承NSOperation时,请把需要执行的任务代码写在main方法中。
  • 不能直接调用NSOperationmain方法。
  • 如果只重写main方法,底层控制变更任务执行完成状态,以及任务退出。但是如果重写了start方法,就需要我们自行控制任务状态。
  • 系统是怎样移除一个isFinished=YESNSOperation的 ? 通过KVO
// 取消操作方法,实质是标记isCancelled状态
- (void)cancel;  

// 判断操作状态方法 
- (BOOL)isFinished;// 判断操作是否已经结束
- (BOOL)isCancelled;// 判断操作是否已经标记为取消
- (BOOL)isExecuting;// 判断操作是否正在在运行
- (BOOL)isReady;// 判断操作是否处于准备就绪状态,这个值和操作的依赖关系相关

// 操作同步 
- (void)waitUntilFinished;// 阻塞当前线程,直到该操作结束。可用于线程执行顺序的同步。
- (void)setCompletionBlock:(void (^)(void))block;// 在当前操作执行完毕时后执行completionBlock
- (void)addDependency:(NSOperation *)op;// 添加依赖,使当前操作依赖于操作 op 的完成
- (void)removeDependency:(NSOperation *)op;// 移除依赖,取消当前操作对操作 op 的依赖
@property (readonly, copy) NSArray<NSOperation *> *dependencies;// 在当前操作开始执行之前完成执行的所有操作对象数组

// 优先级的取值
// 对于添加到队列中的操作,首先进入准备就绪的状态(就绪状态取决于操作之间的依赖关系),然后进入就绪状态的操作的开始执行顺序(非结束执行顺序)由操作之间相对的优先级决定(优先级是操作对象自身的属性)
typedef NS_ENUM(NSInteger, NSOperationQueuePriority) {
    NSOperationQueuePriorityVeryLow = -8L,
    NSOperationQueuePriorityLow = -4L,
    NSOperationQueuePriorityNormal = 0,
    NSOperationQueuePriorityHigh = 4,
    NSOperationQueuePriorityVeryHigh = 8
};
操作队列(Operation Queues)常用属性和方法
  • 用来存放操作的队列。不同于GCD 中的调度队列FIFO(先进先出)的原则。NSOperationQueue 对于添加到队列中的操作,首先进入准备就绪的状态(就绪状态取决于操作之间的依赖关系),然后进入就绪状态的操作的开始执行顺序(非结束执行顺序)由操作之间相对的优先级决定(优先级是操作对象自身的属性)。
  • 操作队列通过设置最大并发操作数(maxConcurrentOperationCount) 来控制并发、串行。
  • NSOperationQueue 为我们提供了两种不同类型的队列:主队列和自定义队列。主队列运行在主线程之上,而自定义队列在后台执行。
  • NSOperation 单独使用时系统同步执行操作,配合 NSOperationQueue 我们能更好的实现异步执行。
// 取消/暂停/恢复操作 
- (void)cancelAllOperations;// 可以取消队列的所有操作
- (BOOL)isSuspended;// 判断队列是否处于暂停状态。 YES 为暂停状态,NO 为恢复状态
- (void)setSuspended:(BOOL)b;// 可设置操作的暂停和恢复,YES 代表暂停队列,NO 代表恢复队列

// 操作同步 
- (void)waitUntilAllOperationsAreFinished;// 阻塞当前线程,直到队列中的操作全部执行完毕

// 添加/获取操作 
- (void)addOperationWithBlock:(void (^)(void))block;// 向队列中添加一个 NSBlockOperation类型操作对象
- (void)addOperations:(NSArray *)ops waitUntilFinished:(BOOL)wait;// 向队列中添加操作数组,wait 标志是否阻塞当前线程直到所有操作结束
- (NSArray *)operations;// 当前在队列中的操作数组(某个操作执行结束后会自动从这个数组清除)
- (NSUInteger)operationCount;// 当前队列中的操作数

// 获取队列 
+ (id)currentQueue;// 获取当前队列,如果当前线程不是在 NSOperationQueue 上运行则返回 nil
+ (id)mainQueue;// 获取主队列
NSOperation实现多线程的步骤
  1. 创建操作:先将需要执行的操作封装到一个NSOperation 对象中。
  2. 创建队列:创建 NSOperationQueue 对象。
  3. 将操作加入到队列中:NSOperation 对象添加到NSOperationQueue 对象中。
  4. 执行的时候,系统就会自动将NSOperationQueue 中的NSOperation取出来,在相应的线程中执行操作。

2、NSOperation的创建

a、使用子类 NSInvocationOperation
// 使用子类 NSInvocationOperation
- (void)useInvocationOperation {
    // 1.创建 NSInvocationOperation 对象
    NSInvocationOperation *invocationOperation = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(invocationOperationEvent) object:nil];

    // 2.调用 start 方法开始执行操作
    [invocationOperation start];
}

- (void)invocationOperationEvent {
    NSLog(@"NSInvocationOperation包含的任务,没有加入队列========%@", [NSThread currentThread]);
}

输出结果显示程序在主线程执行,没有开启新线程。这是因为NSOperation多线程的使用需要配合队列NSOperationQueue

2020-08-18 17:23:33.770247+0800 MultiThreadDemo[26533:2690021] NSInvocationOperation包含的任务,没有加入队列========<NSThread: 0x600003f34d80>{number = 1, name = main}
b、使用子类 NSBlockOperation
- (void)useBlockOperation {
    // 1.创建 NSBlockOperation 对象
    NSBlockOperation *blockOperation = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"NSBlockOperation包含的任务,没有加入队列========%@", [NSThread currentThread]);
    }];
    
    // 2.调用 start 方法开始执行操作
    [blockOperation start];
}

输出结果显示在主线程执行,没有开启新线程。同样的,NSBlockOperation可以配合队列NSOperationQueue来实现多线程。

2020-08-18 17:39:18.363358+0800 MultiThreadDemo[26585:2698280] NSBlockOperation包含的任务,没有加入队列========<NSThread: 0x600002ec0d80>{number = 1, name = main}

但是NSBlockOperation有一个方法addExecutionBlock:,通过这个方法也可以让NSBlockOperation实现多线程。

// addExecutionBlock:实现多线程 
- (void)testNSBlockOperationExecution {
    NSBlockOperation *blockOperation = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"NSBlockOperation运用addExecutionBlock========%@", [NSThread currentThread]);
    }];
    
    [blockOperation addExecutionBlock:^{
        NSLog(@"addExecutionBlock方法添加任务1========%@", [NSThread currentThread]);
    }];
    [blockOperation addExecutionBlock:^{
        NSLog(@"addExecutionBlock方法添加任务2========%@", [NSThread currentThread]);
    }];
    [blockOperation addExecutionBlock:^{
        NSLog(@"addExecutionBlock方法添加任务3========%@", [NSThread currentThread]);
    }];
    
    [blockOperation start];
}

输出结果显示NSBlockOperation创建时block中的任务是在主线程执行,而运用addExecutionBlock加入的任务是在子线程执行的。这里系统自动开启了新线程,开启的线程数是由系统来决定的。

2020-08-18 17:42:47.972752+0800 MultiThreadDemo[26607:2700804] addExecutionBlock方法添加任务3========<NSThread: 0x600001134180>{number = 5, name = (null)}
2020-08-18 17:42:47.972754+0800 MultiThreadDemo[26607:2700806] addExecutionBlock方法添加任务1========<NSThread: 0x600001165a00>{number = 6, name = (null)}
2020-08-18 17:42:47.972757+0800 MultiThreadDemo[26607:2700803] addExecutionBlock方法添加任务2========<NSThread: 0x600001174100>{number = 7, name = (null)}
2020-08-18 17:42:47.972763+0800 MultiThreadDemo[26607:2700607] NSBlockOperation运用addExecutionBlock========<NSThread: 0x60000113cd80>{number = 1, name = main}
c、使用继承自NSOperation的子类

定义一个继承自NSOperation的类,然后重写它的main方法,之后就可以使用这个子类来进行相关的操作了。

@interface CustomOperation : NSOperation

@end

@implementation CustomOperation

// ou should override this method to perform the desired task.
// In your implementation, do not invoke super.
// This method will automatically execute within an autorelease pool provided by NSOperation
- (void)main
{
    for (int i = 0; i < 3; I++)
    {
        NSLog(@"NSOperation的子类CustomOperation======%@",[NSThread currentThread]);
    }
}

@end

@implementation NSOperationViewController

// 使用继承自NSOperation的子类
- (void)testCustomOperation {
    CustomOperation *operation = [[CustomOperation alloc] init];
    [operation start];
}

@end

输出结果为:

2020-08-18 17:51:57.412055+0800 MultiThreadDemo[26670:2707398] NSOperation的子类CustomOperation======<NSThread: 0x600000414f00>{number = 1, name = main}
2020-08-18 17:51:57.412162+0800 MultiThreadDemo[26670:2707398] NSOperation的子类CustomOperation======<NSThread: 0x600000414f00>{number = 1, name = main}
2020-08-18 17:51:57.412223+0800 MultiThreadDemo[26670:2707398] NSOperation的子类CustomOperation======<NSThread: 0x600000414f00>{number = 1, name = main}

3、创建队列 NSOperationQueue

  • 队列NSOperationQueue有一个参数叫做最大并发数:maxConcurrentOperationCount
  • maxConcurrentOperationCount默认为-1,直接并发执行,所以加入到自定义队列中的任务默认就是并发,开启多线程。
  • maxConcurrentOperationCount为1时,则表示不开线程,也就是串行。
  • maxConcurrentOperationCount大于1时,进行并发执行。
  • 系统对最大并发数有一个限制,所以我们即使把maxConcurrentOperationCount设置的很大,系统也会自动调整,所以把最大并发数设置的很大是没有意义的。
// 主队列获取方法
// 凡是添加到主队列中的操作,都会放到主线程中执行
NSOperationQueue *queue = [NSOperationQueue mainQueue];

// 自定义队列创建方法
// 添加到这种队列中的操作,就会自动放到子线程中执行
// 同时包含了:串行、并发功能,默认就是并发,开启多线程
NSOperationQueue *queue = [[NSOperationQueue alloc] init];

4、将操作添加到队列

把任务加入队列,这才是NSOperation的常规使用方式。

a、addOperation
- (void)addOperationToQueue {
    // 1.创建队列,默认并发
    NSOperationQueue *queue = [[NSOperationQueue alloc] init];

    // 2.创建操作
    // 使用 NSInvocationOperation 创建操作1
    NSInvocationOperation *op1 = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(invocationOperationAddOperation) object:nil];

    // 使用 NSInvocationOperation 创建操作2
    NSInvocationOperation *op2 = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(invocationOperationAddOperation) object:nil];

    // 使用 NSBlockOperation 创建操作3
    NSBlockOperation *op3 = [NSBlockOperation blockOperationWithBlock:^{
        for (int i = 0; i < 2; i++) {
            NSLog(@"blockOperationWithBlock ======%@", [NSThread currentThread]);
        }
    }];

    [op3 addExecutionBlock:^{
        NSLog(@"addExecutionBlock ======%@", [NSThread currentThread]);
    }];

    // 3.使用 addOperation: 添加所有操作到队列中
    [queue addOperation:op1];
    [queue addOperation:op2];
    [queue addOperation:op3];
}
b、addOperationWithBlock

这是一个更方便的把任务添加到队列的方法,直接把任务写在block中,添加到任务中:

- (void)addOperationWithBlock
{
    // 创建队列,默认并发
    NSOperationQueue *queue = [[NSOperationQueue alloc] init];
    // 添加操作到队列
    [queue addOperationWithBlock:^{
        for (int i = 0; i < 3; I++)
        {
            NSLog(@"addOperationWithBlock把任务添加到队列======%@", [NSThread currentThread]);
        }
    }];
}

输出结果显示任务确实是在子线程中执行:

2020-08-19 09:52:46.350497+0800 MultiThreadDemo[29040:2948470] addOperationWithBlock把任务添加到队列======<NSThread: 0x600002088240>{number = 5, name = (null)}
2020-08-19 09:52:46.350606+0800 MultiThreadDemo[29040:2948470] addOperationWithBlock把任务添加到队列======<NSThread: 0x600002088240>{number = 5, name = (null)}
2020-08-19 09:52:46.350680+0800 MultiThreadDemo[29040:2948470] addOperationWithBlock把任务添加到队列======<NSThread: 0x600002088240>{number = 5, name = (null)}

5、运用最大并发数实现串行

  • 属性 maxConcurrentOperationCount,叫做最大并发操作数。用来控制一个特定队列中可以有多少个操作同时参与并发执行。
  • maxConcurrentOperationCount默认情况下为-1,表示不进行限制,可进行并发执行。
  • maxConcurrentOperationCount 为1时,队列为串行队列,只能串行执行。
  • maxConcurrentOperationCount大于1时,队列为并发队列。操作并发执行,当然这个值不应超过系统限制,即使自己设置一个很大的值,系统也会自动调整为 min {自己设定的值,系统设定的默认最大值}
  • 这里 maxConcurrentOperationCount控制的不是并发线程的数量,而是一个队列中同时能并发执行的最大操作数,而且一个操作也并非只能在一个线程中运行,开启线程数量是由系统决定的。
- (void)maxConcurrentOperationCount
{
    // 创建队列,默认并发
    NSOperationQueue *queue = [[NSOperationQueue alloc] init];
    // 最大并发数为1,串行
    queue.maxConcurrentOperationCount = 1;
    // 最大并发数为2,并发
//    queue.maxConcurrentOperationCount = 2;
    
    // 添加操作到队列1
    [queue addOperationWithBlock:^{
        for (int i = 0; i < 3; I++)
        {
            NSLog(@"addOperationWithBlock把操作添加到队列1======%@", [NSThread currentThread]);
        }
    }];
    
    // 添加操作到队列
    [queue addOperationWithBlock:^{
        for (int i = 0; i < 3; I++)
        {
            NSLog(@"addOperationWithBlock把操作添加到队列2======%@", [NSThread currentThread]);
        }
    }];
    
    // 添加操作到队列
    [queue addOperationWithBlock:^{
        for (int i = 0; i < 3; I++)
        {
            NSLog(@"addOperationWithBlock把操作添加到队列3======%@", [NSThread currentThread]);
        }
    }];
}

输出结果显示,当最大并发数为1的时候,虽然开启了线程,但是任务是顺序执行的,所以实现了串行。

2020-08-19 10:02:30.441069+0800 MultiThreadDemo[29090:2955023] addOperationWithBlock把操作添加到队列1======<NSThread: 0x600000fef580>{number = 3, name = (null)}
2020-08-19 10:02:30.441184+0800 MultiThreadDemo[29090:2955023] addOperationWithBlock把操作添加到队列1======<NSThread: 0x600000fef580>{number = 3, name = (null)}
2020-08-19 10:02:30.441263+0800 MultiThreadDemo[29090:2955023] addOperationWithBlock把操作添加到队列1======<NSThread: 0x600000fef580>{number = 3, name = (null)}
2020-08-19 10:02:30.441355+0800 MultiThreadDemo[29090:2955023] addOperationWithBlock把操作添加到队列2======<NSThread: 0x600000fef580>{number = 3, name = (null)}
2020-08-19 10:02:30.441487+0800 MultiThreadDemo[29090:2955023] addOperationWithBlock把操作添加到队列2======<NSThread: 0x600000fef580>{number = 3, name = (null)}
2020-08-19 10:02:30.441554+0800 MultiThreadDemo[29090:2955023] addOperationWithBlock把操作添加到队列2======<NSThread: 0x600000fef580>{number = 3, name = (null)}
2020-08-19 10:02:30.441620+0800 MultiThreadDemo[29090:2955023] addOperationWithBlock把操作添加到队列3======<NSThread: 0x600000fef580>{number = 3, name = (null)}
2020-08-19 10:02:30.441961+0800 MultiThreadDemo[29090:2955023] addOperationWithBlock把操作添加到队列3======<NSThread: 0x600000fef580>{number = 3, name = (null)}
2020-08-19 10:02:30.442028+0800 MultiThreadDemo[29090:2955023] addOperationWithBlock把操作添加到队列3======<NSThread: 0x600000fef580>{number = 3, name = (null)}

尝试把上面的最大并发数变为2,会发现任务就变成了并发执行。

2020-08-19 10:03:12.545954+0800 MultiThreadDemo[29103:2955900] addOperationWithBlock把操作添加到队列2======<NSThread: 0x600003636540>{number = 4, name = (null)}
2020-08-19 10:03:12.545964+0800 MultiThreadDemo[29103:2955901] addOperationWithBlock把操作添加到队列1======<NSThread: 0x600003678780>{number = 6, name = (null)}
2020-08-19 10:03:12.546061+0800 MultiThreadDemo[29103:2955900] addOperationWithBlock把操作添加到队列2======<NSThread: 0x600003636540>{number = 4, name = (null)}
2020-08-19 10:03:12.546073+0800 MultiThreadDemo[29103:2955901] addOperationWithBlock把操作添加到队列1======<NSThread: 0x600003678780>{number = 6, name = (null)}
2020-08-19 10:03:12.546132+0800 MultiThreadDemo[29103:2955901] addOperationWithBlock把操作添加到队列1======<NSThread: 0x600003678780>{number = 6, name = (null)}
2020-08-19 10:03:12.546130+0800 MultiThreadDemo[29103:2955900] addOperationWithBlock把操作添加到队列2======<NSThread: 0x600003636540>{number = 4, name = (null)}
2020-08-19 10:03:12.546241+0800 MultiThreadDemo[29103:2955900] addOperationWithBlock把操作添加到队列3======<NSThread: 0x600003636540>{number = 4, name = (null)}
2020-08-19 10:03:12.546396+0800 MultiThreadDemo[29103:2955900] addOperationWithBlock把操作添加到队列3======<NSThread: 0x600003636540>{number = 4, name = (null)}
2020-08-19 10:03:12.546712+0800 MultiThreadDemo[29103:2955900] addOperationWithBlock把操作添加到队列3======<NSThread: 0x600003636540>{number = 4, name = (null)}

6、取消或者暂停操作

  • cancelAllOperations:方法可以取消队列NSOperationQueue的所有操作,该方法的底层是调用队列中的每个操作的cancel方法,将所有操作取消。
  • 取消之后,当前正在执行的操作的下一个操作将不再执行,而且永远都不在执行,就像后面的所有任务都从队列里面移除了一样。
  • 取消操作是不可以恢复的。
  • 暂停表示不继续执行队列中的下一个任务,暂停操作是可以恢复的。
  • 暂停和取消不是立刻取消当前操作,而是等当前的操作执行完之后不再进行新的操作。
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
// 取消所有操作
[queue cancelAllOperations];

NSInvocationOperation *invocationOperation = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(invocationOperation) object:nil];
// 取消NSOperation的某个操作
[invocationOperation cancel];

//设置暂停和恢复,suspended设置为YES表示暂停,suspended设置为NO表示恢复
if (queue.isSuspended) {
    queue.suspended = NO;
}
else
{
    queue.suspended = YES;
}

7、NSOperation 操作依赖

某一个操作(operation2)依赖于另一个操作(operation1),只有当operation1执行完毕,才能执行operation2,这时,就是操作依赖大显身手的时候了。

// 添加依赖关系
- (void)addDependency
{
    // 并发队列
    NSOperationQueue *queue = [[NSOperationQueue alloc] init];
    
    // 操作1
    NSBlockOperation *operation1 = [NSBlockOperation blockOperationWithBlock:^{
        for (int i = 0; i < 3; I++)
        {
            NSLog(@"operation1======%@", [NSThread  currentThread]);
        }
    }];
    
    // 操作2
    NSBlockOperation *operation2 = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"****operation2依赖于operation1,只有当operation1执行完毕,operation2才会执行****");
        for (int i = 0; i < 3; I++)
        {
            NSLog(@"operation2======%@", [NSThread  currentThread]);
        }
    }];
    
    // 使操作2依赖于操作1
    [operation2 addDependency:operation1];
    
    // 把操作加入队列
    [queue addOperation:operation1];
    [queue addOperation:operation2];
}

输出结果显示操作2总是在操作1之后执行:

2020-08-19 10:27:26.641070+0800 MultiThreadDemo[29188:2968280] operation1======<NSThread: 0x6000010a0640>{number = 4, name = (null)}
2020-08-19 10:27:26.641183+0800 MultiThreadDemo[29188:2968280] operation1======<NSThread: 0x6000010a0640>{number = 4, name = (null)}
2020-08-19 10:27:26.641253+0800 MultiThreadDemo[29188:2968280] operation1======<NSThread: 0x6000010a0640>{number = 4, name = (null)}

2020-08-19 10:27:26.641337+0800 MultiThreadDemo[29188:2968280] ****operation2依赖于operation1,只有当operation1执行完毕,operation2才会执行****

2020-08-19 10:27:26.641406+0800 MultiThreadDemo[29188:2968280] operation2======<NSThread: 0x6000010a0640>{number = 4, name = (null)}
2020-08-19 10:27:26.641475+0800 MultiThreadDemo[29188:2968280] operation2======<NSThread: 0x6000010a0640>{number = 4, name = (null)}
2020-08-19 10:27:26.641531+0800 MultiThreadDemo[29188:2968280] operation2======<NSThread: 0x6000010a0640>{number = 4, name = (null)}

8、NSOperation、NSOperationQueue 线程间的通信

// 线程间通信
- (void)communication {
    // 1.创建队列
    NSOperationQueue *queue = [[NSOperationQueue alloc]init];

    // 2.添加操作
    [queue addOperationWithBlock:^{
        // 异步进行耗时操作
        // 回到主线程
        [[NSOperationQueue mainQueue] addOperationWithBlock:^{
            // 进行一些 UI 刷新等操作
        }];
    }];
}

四、多线程与锁

锁的概念

锁是最常用的同步工具。一段代码段在同一个时间只能允许被一个线程访问,比如一个线程A进入加锁代码之后由于已经加锁,另一个线程B就无法访问,只有等待前一个线程A执行完加锁代码后解锁,B线程才能访问加锁代码。不要将过多的其他操作代码放到里面,否则一个线程执行的时候另一个线程就一直在等待,就无法发挥多线程的作用了。

使用锁的场景

如下载解压缩,排序显示,加载多张图片然后在都下载完成后合成一张整图等等。

问题

使用经典的存钱-取钱案例。假设我们账号里面有100元,每次存钱都存10元,每次取钱都取20元。存5次,取5次。那么就是应该最终剩下50元才对。如果我们把存在和取钱在不同的线程中访问的时候,如果不加锁,就很可能导致问题。

// 存钱
- (void)saveMoney
{
    // 每次存钱都存10元
    int oldMoney = self.money;
    sleep(.2);
    oldMoney += 10;
    self.money = oldMoney;
    
    NSLog(@"存10元,还剩%d元 - %@", oldMoney, [NSThread currentThread]);
}

// 取钱
- (void)drawMoney
{
    // 每次取钱都取20元
    int oldMoney = self.money;
    sleep(.2);
    oldMoney -= 20;
    self.money = oldMoney;
    
    NSLog(@"取20元,还剩%d元 - %@", oldMoney, [NSThread currentThread]);
}

// 存钱、取钱演示
- (void)moneyTest
{
    // 最初账号里面有100元
    self.money = 100;
    
    // 全局队列
    dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
    
    // 异步执行全局队列中的任务:存钱,存5次
    dispatch_async(queue, ^{
        for (int i = 0; i < 5; I++)
        {
            [self saveMoney];
        }
    });
    
    // 异步执行全局队列中的任务:取钱,取5次
    dispatch_async(queue, ^{
        for (int i = 0; i < 5; I++)
        {
            [self drawMoney];
        }
    });
}

输出结果为:

2020-07-16 14:55:19.990567+0800 多线程Demo[60071:18849406] 取20元,还剩80元 - <NSThread: 0x600002839a40>{number = 3, name = (null)}
2020-07-16 14:55:19.990571+0800 多线程Demo[60071:18849404] 存10元,还剩110元 - <NSThread: 0x600002876280>{number = 7, name = (null)}
2020-07-16 14:55:19.990706+0800 多线程Demo[60071:18849406] 取20元,还剩90元 - <NSThread: 0x600002839a40>{number = 3, name = (null)}
2020-07-16 14:55:19.990696+0800 多线程Demo[60071:18849404] 存10元,还剩100元 - <NSThread: 0x600002876280>{number = 7, name = (null)}
2020-07-16 14:55:19.990774+0800 多线程Demo[60071:18849406] 取20元,还剩80元 - <NSThread: 0x600002839a40>{number = 3, name = (null)}
2020-07-16 14:55:19.990783+0800 多线程Demo[60071:18849404] 存10元,还剩90元 - <NSThread: 0x600002876280>{number = 7, name = (null)}
2020-07-16 14:55:19.990840+0800 多线程Demo[60071:18849404] 存10元,还剩100元 - <NSThread: 0x600002876280>{number = 7, name = (null)}
2020-07-16 14:55:19.990847+0800 多线程Demo[60071:18849406] 取20元,还剩70元 - <NSThread: 0x600002839a40>{number = 3, name = (null)}
2020-07-16 14:55:19.990894+0800 多线程Demo[60071:18849404] 存10元,还剩80元 - <NSThread: 0x600002876280>{number = 7, name = (null)}
2020-07-16 14:55:19.991257+0800 多线程Demo[60071:18849406] 取20元,还剩60元 - <NSThread: 0x600002839a40>{number = 3, name = (null)}

从结果上来看,明显不是预期的那样。这是因为,正常情况下,取20元之后,还剩下80元,然后存10元,剩余90元没问题。但是我们是不同线程同时操作的时候,可能导致的情况是,正在取钱的是,来存钱了,也就是20元还没取出来,就去存钱,误以为当前还是100元,给出结果是100 + 10 = 110 元,然而实际上应该是 100 - 20 + 10 = 90 元,这样的话,就导致了数据的紊乱。

解决的办法就是线程锁,当存钱的时候,先去加锁,然后存完了,再放开锁。取钱也是一样,这样就保证数据的一致性。举个加锁解锁流程的例子:

@implementation ThreadSafeQueue
{
    NSMutableArray *_elements;
    NSLock *_lock;
}

- (instancetype)init
{
    self = [super init];
    if (self) {
        _elements = [NSMutableArray array];
        _lock = [[NSLock alloc] init];
    }
    return self;
}

- (void)push:(id)element
{
    [_lock lock];
    [_elements addObject:element];
    [_lock unlock];
}

@end

init 方法初始化了一个 _elements 数组和一个NSLock实例。这个类还有个push:方法,它先获取锁、然后向数组中插入元素、最终释放锁。可能会有许多线程同时调用 push:方法,但是[_elements addObject:element]这行代码在任何时候将只会在一个线程上运行。步骤如下:

  1. 线程 A 调用 push: 方法
  2. 线程 B 调用 push: 方法
  3. 线程 B 调用 [_lock lock],因为当前没有其他线程持有锁,线程 B 获得了锁
  4. 线程 A 调用 [_lock lock],但是锁已经被线程 B 占了所以方法调用并没有返回,这会暂停线程 A 的执行
  5. 线程 B 向 _elements 添加元素后调用 [_lock unlock]。当这些发生时,线程 A 的 [_lock lock] 方法返回,并继续将自己的元素插入 _elements
举例

方案一:OSSpinLock自旋锁

OSSpinLock叫做自旋锁,等待锁的线程会处于忙等(busy-wait)状态,一直占用着CPU资源,即循环等待询问,不释放当前资源,通过用于轻量级的数据访问,如简单的int+1/-1操作。

什么情况使用自旋锁比较划算?
  • 预计线程等待锁的时间很短
  • 加锁的代码(临界区)经常被调用,但竞争情况很少发生
  • CPU资源不紧张
  • 多核处理器
使用说明
// 需要导入头文件
#import <libkern/OSAtomic.h>
// 初始化
OSSpinLock lock = OS_SPINLOCK_INIT;
//尝试加锁(如果不需要等待,就直接加锁,返回true。如果需要等待,就不加锁,返回false)
BOOL res = OSSpinLockTry(lock);
//加锁
OSSpinLockLock(lock);
//解锁
OSSpinLockUnlock(lock);
解答本题
#import "OSSpinLockDemo.h"
#import <libkern/OSAtomic.h>

@interface OSSpinLockDemo()

@property (assign, nonatomic) OSSpinLock moneyLock;// 自旋锁

@end

@implementation OSSpinLockDemo

- (instancetype)init
{
    self = [super init];
    if (self) {
        self.moneyLock = OS_SPINLOCK_INIT;// 初始化
    }
    return self;
}

// 存钱
- (void)saveMoney
{
    OSSpinLockLock(&_moneyLock);//加锁
    [super saveMoney];
    OSSpinLockUnlock(&_moneyLock);//解锁
}

// 取钱
- (void)drawMoney
{
    OSSpinLockLock(&_moneyLock);//加锁
    [super drawMoney];
    OSSpinLockUnlock(&_moneyLock);//解锁
}

@end

输出结果为:

2020-07-16 15:27:02.262793+0800 多线程Demo[60201:18867775] 存10元,还剩110元 - <NSThread: 0x600003940a40>{number = 5, name = (null)}
2020-07-16 15:27:02.262937+0800 多线程Demo[60201:18867775] 存10元,还剩120元 - <NSThread: 0x600003940a40>{number = 5, name = (null)}
2020-07-16 15:27:02.263043+0800 多线程Demo[60201:18867775] 存10元,还剩130元 - <NSThread: 0x600003940a40>{number = 5, name = (null)}
2020-07-16 15:27:02.263153+0800 多线程Demo[60201:18867775] 存10元,还剩140元 - <NSThread: 0x600003940a40>{number = 5, name = (null)}
2020-07-16 15:27:02.263215+0800 多线程Demo[60201:18867775] 存10元,还剩150元 - <NSThread: 0x600003940a40>{number = 5, name = (null)}
2020-07-16 15:27:02.263800+0800 多线程Demo[60201:18867774] 取20元,还剩130元 - <NSThread: 0x60000391c840>{number = 6, name = (null)}
2020-07-16 15:27:02.263878+0800 多线程Demo[60201:18867774] 取20元,还剩110元 - <NSThread: 0x60000391c840>{number = 6, name = (null)}
2020-07-16 15:27:02.263956+0800 多线程Demo[60201:18867774] 取20元,还剩90元 - <NSThread: 0x60000391c840>{number = 6, name = (null)}
2020-07-16 15:27:02.264034+0800 多线程Demo[60201:18867774] 取20元,还剩70元 - <NSThread: 0x60000391c840>{number = 6, name = (null)}
2020-07-16 15:27:02.264119+0800 多线程Demo[60201:18867774] 取20元,还剩50元 - <NSThread: 0x60000391c840>{number = 6, name = (null)}

由输出可知,能保证线程安全,数据没有错乱。但是OSSpinLock已经被废弃掉了。

'OSSpinLock' is deprecated: first deprecated in iOS 10.0 - Use os_unfair_lock() from <os/lock.h> instead

废弃原因:如果一个低优先级的线程获得锁并访问共享资源,这时一个高优先级的线程也尝试获得这个锁,它会处于spin lock的忙等状态从而占用大量CPU。此时低优先级线程无法与高优先级线程争夺 CPU 时间,从而导致任务迟迟完不成、无法释放lock,这就是优先级反转。这并不只是理论上的问题,开发者已经遇到很多次这个问题,于是苹果工程师停用了 OSSpinLock

废弃举例解释说明:开启了thread1(优先级最高)、thread2(优先级最低)这两条线程来执行相同任务,如果thread2先进来执行,就会先加锁准备执行任务;这时候thread1刚好进来了,发现线程已经被加过锁了那它只能忙等;忙等相当于是在while循环等待,这也是需要消耗CPU给分配的资源的,由于thread1优先级最高肯定会分配到更多的资源,这样可能会造成thread2没有资源可被利用无法继续执行自己的代码,没发继续执行也就没办法解锁了,thread2家的这把锁就无法释放了!thread2的无法释放,thread1一直在忙等;最终就造成死锁咯 !解决这种情况需要把忙等该为休眠,就是等待的这个线程让他休眠,而这种技术在os_unfair_lock中实现了!

方案二:os_unfair_lock互斥锁

os_unfair_lock用于取代不安全的OSSpinLock , 从底层调用看,等待os_unfair_lock锁的线程会处于休眠状态,并非忙等。

什么情况使用互斥锁比较划算?
  • 预计线程等待锁的时间较长
  • 单核处理器
  • 临界区有IO操作
  • 临界区代码复杂或者循环量大
  • 临界区竞争非常激烈
使用说明
// 需要导入头文件
#import <os/lock.h>
// 初始化
os_unfair_lock lock = OS_UNFAIR_LOCK_INIT;
//尝试加锁(如果不需要等待,就直接加锁,返回true。如果需要等待,就不加锁,返回false)
BOOL res = os_unfair_lock_trylock(&lock);
//加锁
os_unfair_lock_lock(&lock);
//解锁
os_unfair_lock_unlock(&lock);
解答本题
#import "os_unfair_lockDemo.h"
#import <os/lock.h>

@interface os_unfair_lockDemo()

@property (nonatomic ,assign) os_unfair_lock moneyLock;

@end

@implementation os_unfair_lockDemo

- (instancetype)init
{
    self = [super init];
    if (self) {
        self.moneyLock = OS_UNFAIR_LOCK_INIT;
    }
    return self;
}

// 存钱
- (void)saveMoney
{
    os_unfair_lock_lock(&_moneyLock);//加锁
    [super saveMoney];
    os_unfair_lock_unlock(&_moneyLock);//解锁
}

// 取钱
- (void)drawMoney
{
    os_unfair_lock_lock(&_moneyLock);//加锁
    [super drawMoney];
    os_unfair_lock_unlock(&_moneyLock);//解锁
}

@end

输出结果为:

2020-07-16 15:38:15.271635+0800 多线程Demo[60261:18875123] 存10元,还剩110元 - <NSThread: 0x600003cb5c80>{number = 8, name = (null)}
2020-07-16 15:38:15.271781+0800 多线程Demo[60261:18875123] 存10元,还剩120元 - <NSThread: 0x600003cb5c80>{number = 8, name = (null)}
2020-07-16 15:38:15.271862+0800 多线程Demo[60261:18875123] 存10元,还剩130元 - <NSThread: 0x600003cb5c80>{number = 8, name = (null)}
2020-07-16 15:38:15.271968+0800 多线程Demo[60261:18875123] 存10元,还剩140元 - <NSThread: 0x600003cb5c80>{number = 8, name = (null)}
2020-07-16 15:38:15.272050+0800 多线程Demo[60261:18875123] 存10元,还剩150元 - <NSThread: 0x600003cb5c80>{number = 8, name = (null)}
2020-07-16 15:38:15.272124+0800 多线程Demo[60261:18875120] 取20元,还剩130元 - <NSThread: 0x600003cb1e00>{number = 7, name = (null)}
2020-07-16 15:38:15.272196+0800 多线程Demo[60261:18875120] 取20元,还剩110元 - <NSThread: 0x600003cb1e00>{number = 7, name = (null)}
2020-07-16 15:38:15.272301+0800 多线程Demo[60261:18875120] 取20元,还剩90元 - <NSThread: 0x600003cb1e00>{number = 7, name = (null)}
2020-07-16 15:38:15.272825+0800 多线程Demo[60261:18875120] 取20元,还剩70元 - <NSThread: 0x600003cb1e00>{number = 7, name = (null)}
2020-07-16 15:38:15.272945+0800 多线程Demo[60261:18875120] 取20元,还剩50元 - <NSThread: 0x600003cb1e00>{number = 7, name = (null)}

同样实现了保证数据安全的效果。

买火车票问题

购买火车票,多个窗口卖票(并发),票卖出去之后要把库存减掉(同步),多个窗口出票成功(并发)。

var tickets: [Int] = [Int]()

@IBAction func onThread() {
    let que = DispatchQueue.init(label: "com.jk.thread", attributes: .concurrent)
    
    mutex()
    
    //生成100张票
    for i in 0..<100 {
        tickets.append(i)
    }
    
    //北京卖票窗口
    que.async {
        self.saleTicket()
    }
    
    //上海卖票窗口
    que.async {
        self.saleTicket()
    }
}

//生成一个锁
var lock = pthread_mutex_t.init()

func mutex() {
    //设置属性
    var attr: pthread_mutexattr_t = pthread_mutexattr_t()
    pthread_mutexattr_init(&attr)
    pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE)
    let err = pthread_mutex_init(&self.lock, &attr)
    pthread_mutexattr_destroy(&attr)
    
    switch err {
    case 0:
        // Success
        break
        
    case EAGAIN:
        fatalError("Could not create mutex: EAGAIN (The system temporarily lacks the resources to create another mutex.)")
        
    case EINVAL:
        fatalError("Could not create mutex: invalid attributes")
        
    case ENOMEM:
        fatalError("Could not create mutex: no memory")
        
    default:
        fatalError("Could not create mutex, unspecified error \(err)")
    }
}


func saleTicket() {
    while true {
        //关门,执行任务
        pthread_mutex_lock(&lock)
        
        if tickets.count > 0 {
            print("剩余票数", tickets.count, "卖票窗口", Thread.current)
            tickets.removeLast()
            Thread.sleep(forTimeInterval: 0.2)
        }
        else {
            print("票已经卖完了")
            
            //开门,让其他任务可以执行
            pthread_mutex_unlock(&lock)
            
            break
        }
        
        //开门,让其他任务可以执行
        pthread_mutex_unlock(&lock)
    }
}

deinit {
    pthread_mutex_destroy(&lock)
}

输出结果为:

剩余票数 100 卖票窗口 <NSThread: 0x1c446f7c0>{number = 5, name = (null)}
剩余票数 99 卖票窗口 <NSThread: 0x1c0472900>{number = 6, name = (null)}
剩余票数 98 卖票窗口 <NSThread: 0x1c446f7c0>{number = 5, name = (null)}
剩余票数 97 卖票窗口 <NSThread: 0x1c0472900>{number = 6, name = (null)}
剩余票数 96 卖票窗口 <NSThread: 0x1c446f7c0>{number = 5, name = (null)}
.................
剩余票数 35 卖票窗口 <NSThread: 0x1c0472900>{number = 6, name = (null)}
剩余票数 34 卖票窗口 <NSThread: 0x1c446f7c0>{number = 5, name = (null)}
剩余票数 33 卖票窗口 <NSThread: 0x1c0472900>{number = 6, name = (null)}
剩余票数 32 卖票窗口 <NSThread: 0x1c446f7c0>{number = 5, name = (null)}
.................
剩余票数 2 卖票窗口 <NSThread: 0x1c446f7c0>{number = 5, name = (null)}
剩余票数 1 卖票窗口 <NSThread: 0x1c0472900>{number = 6, name = (null)}
票已经卖完了
票已经卖完了

方案三:pthread_mutex互斥锁

pthread_mutex叫做”互斥锁”,等待锁的线程会处于休眠状态。主要包括加锁pthread_mutex_lock()、解锁pthread_mutex_unlock()和测试加锁pthread_mutex_trylock()三个操作。pthread_mutex_trylock()语义与pthread_mutex_lock()类似,不同的是在锁已经被占据时返回EBUSY而不是挂起等待。

使用说明
// 需要导入头文件
#import <pthread.h>
// 初始化属性
pthread_mutexattr_t attr;
pthread_mutexattr_init(&attr);
pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_DEFAULT);
// 初始化锁
pthread_mutex_init(mutex, &attr);
// 销毁属性
pthread_mutexattr_destroy(&attr);
// delloc时候,需要销毁锁
pthread_mutex_destroy(&_moneyMutexLock);

其中锁的类型有四种:

#define PTHREAD_MUTEX_NORMAL        0   //一般的锁
#define PTHREAD_MUTEX_ERRORCHECK    1   //错误检查
#define PTHREAD_MUTEX_RECURSIVE     2  //递归锁
#define PTHREAD_MUTEX_DEFAULT       PTHREAD_MUTEX_NORMAL  //默认

当类型是PTHREAD_MUTEX_DEFAULT的时候,相当于null,例如上面的使用可以直接等价于:

pthread_mutex_init(mutex, NULL); //传空,相当于PTHREAD_MUTEX_DEFAULT
解决本题
#import "pthread_mutexDemo.h"
#import <pthread.h>

@interface pthread_mutexDemo()

@property (assign, nonatomic) pthread_mutex_t moneyMutexLock;

@end

@implementation pthread_mutexDemo

// 初始化锁
- (void)initMutexLock:(pthread_mutex_t *)mutex
{
    // 初始化属性
    pthread_mutexattr_t attr;
    pthread_mutexattr_init(&attr);
    pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_DEFAULT);
    // 初始化锁
    pthread_mutex_init(mutex, &attr);
    // 销毁属性
    pthread_mutexattr_destroy(&attr);
    
    // 上面五行相当于下面一行
    // 传空,相当于PTHREAD_MUTEX_DEFAULT
    // pthread_mutex_init(mutex, NULL);
}

- (instancetype)init
{
    self = [super init];
    if (self) {
        [self initMutexLock:&_moneyMutexLock];
    }
    return self;
}

- (void)dealloc
{
    // delloc时候,需要销毁锁
    pthread_mutex_destroy(&_moneyMutexLock);
}

// 存钱
- (void)saveMoney
{
    pthread_mutex_lock(&_moneyMutexLock);//加锁
    [super saveMoney];
    pthread_mutex_unlock(&_moneyMutexLock);//解锁
}

// 取钱
- (void)drawMoney
{
    pthread_mutex_lock(&_moneyMutexLock);//加锁
    [super drawMoney];
    pthread_mutex_unlock(&_moneyMutexLock);//解锁
}

@end

输出结果为:

2020-07-16 15:57:32.337886+0800 多线程Demo[60325:18886591] 存10元,还剩110元 - <NSThread: 0x60000051c080>{number = 5, name = (null)}
2020-07-16 15:57:32.338195+0800 多线程Demo[60325:18886591] 存10元,还剩120元 - <NSThread: 0x60000051c080>{number = 5, name = (null)}
2020-07-16 15:57:32.338277+0800 多线程Demo[60325:18886591] 存10元,还剩130元 - <NSThread: 0x60000051c080>{number = 5, name = (null)}
2020-07-16 15:57:32.338374+0800 多线程Demo[60325:18886591] 存10元,还剩140元 - <NSThread: 0x60000051c080>{number = 5, name = (null)}
2020-07-16 15:57:32.338459+0800 多线程Demo[60325:18886591] 存10元,还剩150元 - <NSThread: 0x60000051c080>{number = 5, name = (null)}
2020-07-16 15:57:32.338550+0800 多线程Demo[60325:18886592] 取20元,还剩130元 - <NSThread: 0x60000050c340>{number = 6, name = (null)}
2020-07-16 15:57:32.338627+0800 多线程Demo[60325:18886592] 取20元,还剩110元 - <NSThread: 0x60000050c340>{number = 6, name = (null)}
2020-07-16 15:57:32.338700+0800 多线程Demo[60325:18886592] 取20元,还剩90元 - <NSThread: 0x60000050c340>{number = 6, name = (null)}
2020-07-16 15:57:32.338788+0800 多线程Demo[60325:18886592] 取20元,还剩70元 - <NSThread: 0x60000050c340>{number = 6, name = (null)}
2020-07-16 15:57:32.338852+0800 多线程Demo[60325:18886592] 取20元,还剩50元 - <NSThread: 0x60000050c340>{number = 6, name = (null)}

同样实现了保证数据安全的效果。

买火车票问题

总共有100张火车票,开启两个线程,北京和上海两个窗口同时卖票,卖一张票就减去库存,使用锁,保证北京和上海卖票的库存是一致的。

- (void)viewDidLoad
{
    [super viewDidLoad];
    
    [self onThread];
}

pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
NSMutableArray *tickets;

- (void)onThread {
    tickets = [NSMutableArray array];
    
    //生成100张票
    for (int i = 0; i < 100; i++)
    {
        [tickets addObject:[NSNumber numberWithInt:i]];
    }
    
    // 线程1 北京卖票窗口
    // 1. 创建线程1: 定义一个pthread_t类型变量
    pthread_t thread1;
    // 2. 开启线程1: 执行任务
    pthread_create(&thread1, NULL, run, NULL);
    // 3. 设置子线程1的状态设置为detached,该线程运行结束后会自动释放所有资源
    pthread_detach(thread1);
    
    // 线程2 上海卖票窗口
    // 1. 创建线程2: 定义一个pthread_t类型变量
    pthread_t thread2;
    // 2. 开启线程2: 执行任务
    pthread_create(&thread2, NULL, run, NULL);
    // 3. 设置子线程2的状态设置为detached,该线程运行结束后会自动释放所有资源
    pthread_detach(thread2);

}

void * run(void *param) {
    while (true) {
        //锁门,执行任务
        pthread_mutex_lock(&mutex);
        
        if (tickets.count > 0)
        {
            NSLog(@"剩余票数%ld, 卖票窗口%@", tickets.count, [NSThread currentThread]);
            [tickets removeLastObject];
            [NSThread sleepForTimeInterval:0.2];
        }
        else
        {
            NSLog(@"票已经卖完了");

            //开门,让其他任务可以执行
            pthread_mutex_unlock(&mutex);

            break;
        }
        
        //开门,让其他任务可以执行
        pthread_mutex_unlock(&mutex);
    }
    return NULL;
}

输出结果为:

2020-08-21 11:21:51.679196+0800 Demo[38057:3948746] 剩余票数100, 卖票窗口<NSThread: 0x60000123c0c0>{number = 5, name = (null)}
2020-08-21 11:21:51.883712+0800 Demo[38057:3948746] 剩余票数99, 卖票窗口<NSThread: 0x60000123c0c0>{number = 5, name = (null)}
2020-08-21 11:21:52.084056+0800 Demo[38057:3948746] 剩余票数98, 卖票窗口<NSThread: 0x60000123c0c0>{number = 5, name = (null)}
.......
2020-08-21 11:22:07.731459+0800 Demo[38057:3948746] 剩余票数21, 卖票窗口<NSThread: 0x60000123c0c0>{number = 5, name = (null)}
2020-08-21 11:22:07.935146+0800 Demo[38057:3948747] 剩余票数20, 卖票窗口<NSThread: 0x600001200700>{number = 6, name = (null)}
2020-08-21 11:22:08.138617+0800 Demo[38057:3948747] 剩余票数19, 卖票窗口<NSThread: 0x600001200700>{number = 6, name = (null)}
........
2020-08-21 11:22:11.585928+0800 Demo[38057:3948747] 剩余票数2, 卖票窗口<NSThread: 0x600001200700>{number = 6, name = (null)}
2020-08-21 11:22:11.790698+0800 Demo[38057:3948747] 剩余票数1, 卖票窗口<NSThread: 0x600001200700>{number = 6, name = (null)}
2020-08-21 11:22:11.996076+0800 Demo[38057:3948747] 票已经卖完了
2020-08-21 11:22:11.996341+0800 Demo[38057:3948746] 票已经卖完了

方案四:pthread_mutex递归锁

pthread_mutex除了有”互斥锁”,还有递归锁。递归锁允许同一个线程对一把锁进行重复加锁。

递归锁的作用

重复上锁导致死锁。

- (void)methodA {
    [_lock lock];
    [self methodB];
    [_lock unlock];
}

=======死锁========

- (void)methodB{
    [_lock lock];
    [self methodA];
    [_lock unlock];
}

递归锁除了解决重复上锁导致的死锁问题,也可以解决递归方法调用引起的多线程问题。

- (void)methodA {
    [_recursiveLock lock];
    [self methodB];
    [_recursiveLock unlock];
}

=======正常========

- (void)methodB {
    [_recursiveLock lock];
    // 操作逻辑
    [_recursiveLock unlock];
}
使用说明
// 初始化属性
pthread_mutexattr_t attr;
pthread_mutexattr_init(&attr);
pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE);
// 初始化锁
pthread_mutex_init(mutex, &attr);
// 销毁属性
pthread_mutexattr_destroy(&attr);

其中锁的类型有四种:

#define PTHREAD_MUTEX_NORMAL        0   //一般的锁
#define PTHREAD_MUTEX_ERRORCHECK    1   // 错误检查
#define PTHREAD_MUTEX_RECURSIVE     2  //递归锁
#define PTHREAD_MUTEX_DEFAULT       PTHREAD_MUTEX_NORMAL  //默认
实际使用
#import "MutexRecursiveLockDemo.h"
#import <pthread.h>

@interface MutexRecursiveLockDemo()

@property (assign, nonatomic) pthread_mutex_t MutexLock;

@end

@implementation MutexRecursiveLockDemo

// 初始化锁
- (void)initMutexLock:(pthread_mutex_t *)mutex
{
    // 初始化属性
    pthread_mutexattr_t attr;
    pthread_mutexattr_init(&attr);
    pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE);
    // 初始化锁
    pthread_mutex_init(mutex, &attr);
    // 销毁属性
    pthread_mutexattr_destroy(&attr);
}

- (instancetype)init
{
    self = [super init];
    if (self) {
        [self initMutexLock:&_MutexLock];
    }
    return self;
}

- (void)dealloc
{
    // delloc时候,需要销毁锁
    pthread_mutex_destroy(&_MutexLock);
}

// 其他演示
- (void)otherTest
{
    // 第一次进来直接加锁,第二次进来,已经加锁了,还能递归继续加锁
    pthread_mutex_lock(&_MutexLock);
    NSLog(@"加锁 %s",__func__);
    
    static int count = 0;
    if (count < 5) {
        count++;
        NSLog(@"count:%d", count);
        [self otherTest];
    }
    
    NSLog(@"解锁 %s",__func__);
    pthread_mutex_unlock(&_MutexLock);
}

@end

输出结果为:

2020-07-16 16:14:32.617478+0800 多线程Demo[60438:18898572] 加锁 -[MutexRecursiveLockDemo otherTest]
2020-07-16 16:14:32.617593+0800 多线程Demo[60438:18898572] count:1
2020-07-16 16:14:32.617671+0800 多线程Demo[60438:18898572] 加锁 -[MutexRecursiveLockDemo otherTest]
2020-07-16 16:14:32.617736+0800 多线程Demo[60438:18898572] count:2
2020-07-16 16:14:32.617797+0800 多线程Demo[60438:18898572] 加锁 -[MutexRecursiveLockDemo otherTest]
2020-07-16 16:14:32.617850+0800 多线程Demo[60438:18898572] count:3
2020-07-16 16:14:32.617911+0800 多线程Demo[60438:18898572] 加锁 -[MutexRecursiveLockDemo otherTest]
2020-07-16 16:14:32.617964+0800 多线程Demo[60438:18898572] count:4
2020-07-16 16:14:32.618020+0800 多线程Demo[60438:18898572] 加锁 -[MutexRecursiveLockDemo otherTest]
2020-07-16 16:14:32.618133+0800 多线程Demo[60438:18898572] count:5
2020-07-16 16:14:32.618302+0800 多线程Demo[60438:18898572] 加锁 -[MutexRecursiveLockDemo otherTest]
2020-07-16 16:14:32.618408+0800 多线程Demo[60438:18898572] 解锁 -[MutexRecursiveLockDemo otherTest]
2020-07-16 16:14:32.618530+0800 多线程Demo[60438:18898572] 解锁 -[MutexRecursiveLockDemo otherTest]
2020-07-16 16:14:32.618621+0800 多线程Demo[60438:18898572] 解锁 -[MutexRecursiveLockDemo otherTest]
2020-07-16 16:14:32.618758+0800 多线程Demo[60438:18898572] 解锁 -[MutexRecursiveLockDemo otherTest]
2020-07-16 16:14:32.618962+0800 多线程Demo[60438:18898572] 解锁 -[MutexRecursiveLockDemo otherTest]
2020-07-16 16:14:32.619100+0800 多线程Demo[60438:18898572] 解锁 -[MutexRecursiveLockDemo otherTest]

由结果可知,连续加锁五次,是因为每次都递归加锁。所以解锁时候,也需要层层解锁。

方案五:pthread_mutex条件锁

为了演示条件锁的作用,就用生产者消费者来展示效果。

使用说明
// 条件
@property (assign, nonatomic) pthread_cond_t cond; 
// 初始化条件
pthread_cond_init(&_cond, NULL);
// 销毁条件
pthread_cond_destroy(&_cond);
// 数据为空就等待(进入休眠,放开mutex锁,被唤醒后,会再次对mutex加锁)
pthread_cond_wait(&_cond, &_mutex);
// 激活一个等待该条件的线程
pthread_cond_signal(&_cond);
// 激活所有等待该条件的线程
pthread_cond_broadcast(&_cond);
实战演示
#import "MutexConditionLockDemo.h"
#import <pthread.h>

@interface MutexConditionLockDemo()

@property (assign, nonatomic) pthread_mutex_t mutex; // 锁
@property (assign, nonatomic) pthread_cond_t cond; //条件
@property (strong, nonatomic) NSMutableArray *data; //数据源

@end

@implementation MutexConditionLockDemo

- (instancetype)init
{
    self = [super init];
    if (self) {
        // 初始化属性
        pthread_mutexattr_t attr;
        pthread_mutexattr_init(&attr);
        pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE);
        // 初始化锁
        pthread_mutex_init(&_mutex, &attr);
        // 销毁属性
        pthread_mutexattr_destroy(&attr);
        
        // 初始化条件
        pthread_cond_init(&_cond, NULL);
        // 初始化数据源
        self.data = [NSMutableArray array];
    }
    return self;
}

- (void)dealloc
{
    // 销毁锁和条件
    pthread_mutex_destroy(&_mutex);
    pthread_cond_destroy(&_cond);
}

#pragma mark - 生产者-消费者模式

- (void)otherTest
{
    // 开启两个线程分别执行生产者-消费者方法
    [[[NSThread alloc] initWithTarget:self selector:@selector(remove) object:nil] start];
    [[[NSThread alloc] initWithTarget:self selector:@selector(add) object:nil] start];
}

// 线程1:删除数组中的元素
- (void)remove
{
    pthread_mutex_lock(&_mutex);// 加锁
    
    if (self.data.count == 0) {
        // 数据为空就等待(进入休眠,放开mutex锁,被唤醒后,会再次对mutex加锁)
        NSLog(@"remove方法:数据为空就等待(进入休眠,放开mutex锁,被唤醒后,会再次对mutex加锁)");
        pthread_cond_wait(&_cond, &_mutex);
    }
    
    // 删除元素
    [self.data removeLastObject];
    NSLog(@"删除了元素");
    
    pthread_mutex_unlock(&_mutex);// 解锁
}

// 线程2:往数组中添加元素
- (void)add
{
    pthread_mutex_lock(&_mutex);// 加锁
    sleep(1);
    
    // 添加元素
    [self.data addObject:@"xiejiapei"];
    NSLog(@"添加了元素");
    
    // 激活一个等待该条件的线程
    pthread_cond_signal(&_cond);
    
    // 激活所有等待该条件的线程
    // pthread_cond_broadcast(&_cond);
    
    pthread_mutex_unlock(&_mutex);// 解锁
}

@end

输出结果为:

2020-07-16 16:41:02.729032+0800 多线程Demo[60545:18913091] remove方法:数据为空就等待(进入休眠,放开mutex锁,被唤醒后,会再次对mutex加锁)
2020-07-16 16:41:03.733362+0800 多线程Demo[60545:18913092] 添加了元素
2020-07-16 16:41:03.733847+0800 多线程Demo[60545:18913091] 删除了元素

由结果可知,打印完remove方法:数据为空就等待之后,等待了一秒钟,添加元素之后,放开锁,才去删除元素。

方案六:NSLock锁

NSLock是对mutex普通锁的封装。

使用说明
- (void)lock; //加锁
- (void)unlock; //解锁
- (BOOL)tryLock; //尝试加锁,如果加锁失败,就返回NO,加锁成功就返回YES
- (BOOL)lockBeforeDate:(NSDate *)limit; //在给定的时间内尝试加锁,加锁成功就返回YES,如果过了时间还没加上锁,就返回NO。
解决本题
#import "NSLockDemo.h"

@interface NSLockDemo()

@property (nonatomic,strong) NSLock *lock;

@end

@implementation NSLockDemo

- (instancetype)init
{
    self = [super init];
    if (self) {
        self.lock =[[NSLock alloc] init];
    }
    return self;
}

// 存钱
- (void)saveMoney
{
    [self.lock lock];// 加锁
    [super saveMoney];
    [self.lock unlock];// 解锁
}

// 取钱
- (void)drawMoney
{
    [self.lock lock];// 加锁
    [super drawMoney];
    [self.lock unlock];// 解锁
}

@end

输出结果为:

2020-07-16 17:00:50.814750+0800 多线程Demo[60639:18925432] 存10元,还剩110元 - <NSThread: 0x600003c29d40>{number = 6, name = (null)}
2020-07-16 17:00:50.814893+0800 多线程Demo[60639:18925432] 存10元,还剩120元 - <NSThread: 0x600003c29d40>{number = 6, name = (null)}
2020-07-16 17:00:50.814983+0800 多线程Demo[60639:18925432] 存10元,还剩130元 - <NSThread: 0x600003c29d40>{number = 6, name = (null)}
2020-07-16 17:00:50.815052+0800 多线程Demo[60639:18925432] 存10元,还剩140元 - <NSThread: 0x600003c29d40>{number = 6, name = (null)}
2020-07-16 17:00:50.815111+0800 多线程Demo[60639:18925432] 存10元,还剩150元 - <NSThread: 0x600003c29d40>{number = 6, name = (null)}
2020-07-16 17:00:50.815202+0800 多线程Demo[60639:18925433] 取20元,还剩130元 - <NSThread: 0x600003c780c0>{number = 5, name = (null)}
2020-07-16 17:00:50.815270+0800 多线程Demo[60639:18925433] 取20元,还剩110元 - <NSThread: 0x600003c780c0>{number = 5, name = (null)}
2020-07-16 17:00:50.815340+0800 多线程Demo[60639:18925433] 取20元,还剩90元 - <NSThread: 0x600003c780c0>{number = 5, name = (null)}
2020-07-16 17:00:50.815682+0800 多线程Demo[60639:18925433] 取20元,还剩70元 - <NSThread: 0x600003c780c0>{number = 5, name = (null)}
2020-07-16 17:00:50.815756+0800 多线程Demo[60639:18925433] 取20元,还剩50元 - <NSThread: 0x600003c780c0>{number = 5, name = (null)}

同样实现了效果,但是语法却非常简洁。

买火车票问题

购买火车票,多个窗口卖票(并发),票卖出去之后要把库存减掉(同步),多个窗口出票成功(并发)。

var tickets: [Int] = [Int]()

@IBAction func onThread() {
    let que = DispatchQueue.init(label: "com.jk.thread", attributes: .concurrent)
    
    //生成100张票
    for i in 0..<100 {
        tickets.append(i)
    }
    
    //北京卖票窗口
    que.async {
        self.saleTicket()
    }
    
    //上海卖票窗口
    que.async {
        self.saleTicket()
    }
}

//生成一个锁
let lock = NSLock.init()

func saleTicket() {
    while true {
        //关门,执行任务
        lock.lock()
        
        if tickets.count > 0 {
            print("剩余票数", tickets.count, "卖票窗口", Thread.current)
            tickets.removeLast()
            Thread.sleep(forTimeInterval: 0.2)
        }
        else {
            print("票已经卖完了")
            
            //开门,让其他任务可以执行
            lock.unlock()
            
            break
        }
        
        //开门,让其他任务可以执行
        lock.unlock()
    }
}

输出结果为:

剩余票数 100 卖票窗口 <NSThread: 0x1c467d300>{number = 6, name = (null)}
剩余票数 99 卖票窗口 <NSThread: 0x1c4862380>{number = 7, name = (null)}
剩余票数 98 卖票窗口 <NSThread: 0x1c467d300>{number = 6, name = (null)}
剩余票数 97 卖票窗口 <NSThread: 0x1c4862380>{number = 7, name = (null)}
剩余票数 96 卖票窗口 <NSThread: 0x1c467d300>{number = 6, name = (null)}
剩余票数 95 卖票窗口 <NSThread: 0x1c4862380>{number = 7, name = (null)}
.................
剩余票数 4 卖票窗口 <NSThread: 0x1c467d300>{number = 6, name = (null)}
剩余票数 3 卖票窗口 <NSThread: 0x1c4862380>{number = 7, name = (null)}
剩余票数 2 卖票窗口 <NSThread: 0x1c467d300>{number = 6, name = (null)}
剩余票数 1 卖票窗口 <NSThread: 0x1c4862380>{number = 7, name = (null)}
票已经卖完了
票已经卖完了

方案七:NSRecursiveLock锁

NSRecursiveLock也是对mutex递归锁的封装,APINSLock基本一致。

使用说明
- (void)lock; //加锁
- (void)unlock; //解锁
- (BOOL)tryLock; //尝试加锁,如果加锁失败,就返回NO,加锁成功就返回YES
- (BOOL)lockBeforeDate:(NSDate *)limit; //在给定的时间内尝试加锁,加锁成功就返回YES,如果过了时间还没加上锁,就返回NO。
解决本题
#import "NSRecursiveLockDemo.h"

@interface NSRecursiveLockDemo()

@property (nonatomic,strong) NSRecursiveLock *lock;

@end

@implementation NSRecursiveLockDemo

- (instancetype)init
{
    self = [super init];
    if (self) {
        self.lock =[[NSRecursiveLock alloc] init];
    }
    return self;
}

// 存钱
- (void)saveMoney
{
    [self.lock lock];// 加锁
    [super saveMoney];
    [self.lock unlock];// 解锁
}

// 取钱
- (void)drawMoney
{
    [self.lock lock];// 加锁
    [super drawMoney];
    [self.lock unlock];// 解锁
}

@end

输出结果为:

2020-07-16 17:06:39.532564+0800 多线程Demo[60688:18930301] 存10元,还剩110元 - <NSThread: 0x6000019d7040>{number = 3, name = (null)}
2020-07-16 17:06:39.532703+0800 多线程Demo[60688:18930301] 存10元,还剩120元 - <NSThread: 0x6000019d7040>{number = 3, name = (null)}
2020-07-16 17:06:39.532789+0800 多线程Demo[60688:18930301] 存10元,还剩130元 - <NSThread: 0x6000019d7040>{number = 3, name = (null)}
2020-07-16 17:06:39.532871+0800 多线程Demo[60688:18930301] 存10元,还剩140元 - <NSThread: 0x6000019d7040>{number = 3, name = (null)}
2020-07-16 17:06:39.532938+0800 多线程Demo[60688:18930301] 存10元,还剩150元 - <NSThread: 0x6000019d7040>{number = 3, name = (null)}
2020-07-16 17:06:39.533038+0800 多线程Demo[60688:18930298] 取20元,还剩130元 - <NSThread: 0x6000019f8c40>{number = 5, name = (null)}
2020-07-16 17:06:39.533100+0800 多线程Demo[60688:18930298] 取20元,还剩110元 - <NSThread: 0x6000019f8c40>{number = 5, name = (null)}
2020-07-16 17:06:39.533155+0800 多线程Demo[60688:18930298] 取20元,还剩90元 - <NSThread: 0x6000019f8c40>{number = 5, name = (null)}
2020-07-16 17:06:39.533354+0800 多线程Demo[60688:18930298] 取20元,还剩70元 - <NSThread: 0x6000019f8c40>{number = 5, name = (null)}
2020-07-16 17:06:39.533424+0800 多线程Demo[60688:18930298] 取20元,还剩50元 - <NSThread: 0x6000019f8c40>{number = 5, name = (null)}

都是套路。

方案八:NSCondition条件锁

NSCondition是对mutexcond的封装。

使用说明
- (void)wait; //等待
- (BOOL)waitUntilDate:(NSDate *)limit; //在给定时间之前等待
- (void)signal;   // 激活一个等待该条件的线程
- (void)broadcast;  // 激活所有等待该条件的线程
实战演示
#import "NSConditionDemo.h"

@interface NSConditionDemo()

@property (assign, nonatomic) NSCondition *condition; //条件
@property (strong, nonatomic) NSMutableArray *data; //数据源

@end

@implementation NSConditionDemo

- (instancetype)init
{
    self = [super init];
    if (self) {
        // 初始化条件
        self.condition = [[NSCondition alloc] init];
        // 初始化数据源
        self.data = [NSMutableArray array];
    }
    return self;
}

#pragma mark - 生产者-消费者模式

- (void)otherTest
{
    // 开启两个线程分别执行生产者-消费者方法
    [[[NSThread alloc] initWithTarget:self selector:@selector(remove) object:nil] start];
    [[[NSThread alloc] initWithTarget:self selector:@selector(add) object:nil] start];
}

// 线程1:删除数组中的元素
- (void)remove
{
    [self.condition lock];// 加锁
    
    if (self.data.count == 0) {
        // 数据为空就等待(进入休眠,放开mutex锁,被唤醒后,会再次对mutex加锁)
        NSLog(@"remove方法:数据为空就等待(进入休眠,放开mutex锁,被唤醒后,会再次对mutex加锁)");
        [self.condition wait];
    }
    
    // 删除元素
    [self.data removeLastObject];
    NSLog(@"删除了元素");
    
    [self.condition unlock];// 解锁
}

// 线程2:往数组中添加元素
- (void)add
{
    [self.condition lock];// 加锁
    sleep(1);
    
    // 添加元素
    [self.data addObject:@"xiejiapei"];
    NSLog(@"添加了元素");
    
    // 激活一个等待该条件的线程
    [self.condition signal];
    
    // 激活所有等待该条件的线程
    // [self.condition broadcast];
    
    [self.condition unlock];// 解锁
}

@end

输出结果为:

2020-07-16 17:16:14.141622+0800 多线程Demo[60770:18938478] remove方法:数据为空就等待(进入休眠,放开mutex锁,被唤醒后,会再次对mutex加锁)
2020-07-16 17:16:18.849376+0800 多线程Demo[60770:18938479] 添加了元素
2020-07-16 17:16:18.849668+0800 多线程Demo[60770:18938478] 删除了元素

结果同之前一样。

方案九:NSConditionLock

NSConditionLock是对NSCondition的进一步封装,可以设置具体的条件值。

使用说明
@property (readonly) NSInteger condition;

- (instancetype)initWithCondition:(NSInteger)condition NS_DESIGNATED_INITIALIZER;

- (void)lockWhenCondition:(NSInteger)condition;
- (void)unlockWithCondition:(NSInteger)condition;

- (BOOL)tryLock;
- (BOOL)tryLockWhenCondition:(NSInteger)condition;

- (BOOL)lockBeforeDate:(NSDate *)limit;
- (BOOL)lockWhenCondition:(NSInteger)condition beforeDate:(NSDate *)limit;
实战演示
#import "NSConditionLockDemo.h"

@implementation NSConditionLockDemo

- (void)otherTest
{
    //主线程中:初始化lock
    NSConditionLock *lock = [[NSConditionLock alloc] initWithCondition:0];
    
    //线程1
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        [lock lockWhenCondition:2];//当条件为2的时候,加锁
        NSLog(@"线程1加锁");
        
        sleep(2);
        
        NSLog(@"线程1解锁成功");
        [lock unlockWithCondition:3];//当条件为3的时候,解锁
    });
    
    //线程2
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        [lock lockWhenCondition:0];
        NSLog(@"线程2加锁");
        
        sleep(3);
        
        NSLog(@"线程2解锁成功");
        [lock unlockWithCondition:1];
    });
    
    //线程3
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        [lock lockWhenCondition:3];
        NSLog(@"线程3加锁");
        
        sleep(3);
        
        NSLog(@"线程3解锁成功");
        [lock unlockWithCondition:4];
    });
    
    //线程4
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        [lock lockWhenCondition:1];
        NSLog(@"线程4加锁");
        
        sleep(2);
        
        NSLog(@"线程4解锁成功");
        [lock unlockWithCondition:2];
    });
    
}

@end

输出结果为:

2020-07-16 17:35:19.209579+0800 多线程Demo[60847:18948490] 线程2加锁
2020-07-16 17:35:22.209840+0800 多线程Demo[60847:18948490] 线程2解锁成功
2020-07-16 17:35:22.210091+0800 多线程Demo[60847:18948491] 线程4加锁
2020-07-16 17:35:24.212865+0800 多线程Demo[60847:18948491] 线程4解锁成功
2020-07-16 17:35:24.213124+0800 多线程Demo[60847:18948493] 线程1加锁
2020-07-16 17:35:26.218454+0800 多线程Demo[60847:18948493] 线程1解锁成功
2020-07-16 17:35:26.218710+0800 多线程Demo[60847:18948489] 线程3加锁
2020-07-16 17:35:29.222361+0800 多线程Demo[60847:18948489] 线程3解锁成功

由结果可知,NSConditionLock完全能够通过条件值01234......顺序进行加锁解锁。

方案十:dispatch_semaphore信号量

semaphore叫做”信号量”。信号量的初始值,可以用来控制线程并发访问的最大数量。信号量的初始值为1,代表同时只允许1条线程访问资源,保证线程同步。每当发送一个信号通知,则信号量+1;每当发送一个等待信号时信号量-1;如果信号量为0则信号会处于等待状态,直到信号量大于0开始执行。简单地说就是洗手间只有一个坑位,外面进来一个人把门关上,其他人排队,这个人把门打开出去之后,可以再进来一个人。

通常等待信号量和发送信号量的函数是成对出现的。并发执行任务时候,在当前任务执行之前,用dispatch_semaphore_wait函数进行等待(阻塞),直到上一个任务执行完毕后且通过dispatch_semaphore_signal函数发送信号量(使信号量的值加1),dispatch_semaphore_wait函数收到信号量之后判断信号量的值大于等于1,会再对信号量的值减1,然后当前任务可以执行,执行完毕当前任务后,再通过dispatch_semaphore_signal函数发送信号量(使信号量的值加1),通知执行下一个任务……如此一来,通过信号量,就达到了并发队列中的任务同步执行的要求。

实战演示

使用一:解决本题
初始化先设置1,然后每次取钱存钱之前都调用dispatch_semaphore_wait,取钱存钱之后都调用dispatch_semaphore_signal

#import "SemaphoreDemo.h"

@interface SemaphoreDemo()

@property (strong, nonatomic) dispatch_semaphore_t moneySemaphore;// 金额

@end

@implementation SemaphoreDemo

- (instancetype)init
{
    self = [super init];
    if (self) {
        // 创建信号量
        self.moneySemaphore = dispatch_semaphore_create(1);
    }
    return self;
}

// 存钱
- (void)saveMoney
{
    dispatch_semaphore_wait(self.moneySemaphore, DISPATCH_TIME_FOREVER);// 等待信号量
    
    [super saveMoney];
    
    dispatch_semaphore_signal(self.moneySemaphore);// 发送信号量
}

// 取钱
- (void)drawMoney
{
    dispatch_semaphore_wait(self.moneySemaphore, DISPATCH_TIME_FOREVER);// 等待信号量
    
    [super drawMoney];
    
    dispatch_semaphore_signal(self.moneySemaphore);// 发送信号量
}

@end

输出结果为:

2020-07-16 17:47:30.413564+0800 多线程Demo[60921:18957636] 存10元,还剩110元 - <NSThread: 0x600001c42040>{number = 4, name = (null)}
2020-07-16 17:47:30.413720+0800 多线程Demo[60921:18957635] 取20元,还剩90元 - <NSThread: 0x600001c00e80>{number = 5, name = (null)}
2020-07-16 17:47:30.413849+0800 多线程Demo[60921:18957636] 存10元,还剩100元 - <NSThread: 0x600001c42040>{number = 4, name = (null)}
2020-07-16 17:47:30.413995+0800 多线程Demo[60921:18957635] 取20元,还剩80元 - <NSThread: 0x600001c00e80>{number = 5, name = (null)}
2020-07-16 17:47:30.414084+0800 多线程Demo[60921:18957636] 存10元,还剩90元 - <NSThread: 0x600001c42040>{number = 4, name = (null)}
2020-07-16 17:47:30.414168+0800 多线程Demo[60921:18957635] 取20元,还剩70元 - <NSThread: 0x600001c00e80>{number = 5, name = (null)}
2020-07-16 17:47:30.414252+0800 多线程Demo[60921:18957636] 存10元,还剩80元 - <NSThread: 0x600001c42040>{number = 4, name = (null)}
2020-07-16 17:47:30.414349+0800 多线程Demo[60921:18957635] 取20元,还剩60元 - <NSThread: 0x600001c00e80>{number = 5, name = (null)}
2020-07-16 17:47:30.414720+0800 多线程Demo[60921:18957636] 存10元,还剩70元 - <NSThread: 0x600001c42040>{number = 4, name = (null)}
2020-07-16 17:47:30.414797+0800 多线程Demo[60921:18957635] 取20元,还剩50元 - <NSThread: 0x600001c00e80>{number = 5, name = (null)}

使用二:购买火车票,多个窗口卖票(并发),票卖出去之后要把库存减掉(同步),多个窗口出票成功(并发)。

var tickets: [Int] = [Int]()

@IBAction func onThread() {
    let que = DispatchQueue.init(label: "com.jk.thread", attributes: .concurrent)
    
    //生成100张票
    for i in 0..<100 {
        tickets.append(i)
    }
    
    //北京卖票窗口
    que.async {
        self.saleTicket()
    }
    
    //上海卖票窗口
    que.async {
        self.saleTicket()
    }
}

//存在一个坑位
let semp = DispatchSemaphore.init(value: 1)

func saleTicket() {
    while true {
        //占坑,坑位减一
        semp.wait()
        
        if tickets.count > 0 {
            print("剩余票数", tickets.count, "卖票窗口", Thread.current)
            tickets.removeLast()
            Thread.sleep(forTimeInterval: 0.2)
        }
        else {
            print("票已经卖完了")
            
            //释放占坑,坑位加一
            semp.signal()
            
            break
        }
        
        //释放占坑,坑位加一
        semp.signal()
    }
}

输出结果是:

剩余票数 100 卖票窗口 <NSThread: 0x1c0472e40>{number = 6, name = (null)}
剩余票数 99 卖票窗口 <NSThread: 0x1c027b540>{number = 4, name = (null)}
剩余票数 98 卖票窗口 <NSThread: 0x1c0472e40>{number = 6, name = (null)}
剩余票数 97 卖票窗口 <NSThread: 0x1c027b540>{number = 4, name = (null)}
剩余票数 96 卖票窗口 <NSThread: 0x1c0472e40>{number = 6, name = (null)}
剩余票数 95 卖票窗口 <NSThread: 0x1c027b540>{number = 4, name = (null)}
.................
剩余票数 4 卖票窗口 <NSThread: 0x1c0472e40>{number = 6, name = (null)}
剩余票数 3 卖票窗口 <NSThread: 0x1c027b540>{number = 4, name = (null)}
剩余票数 2 卖票窗口 <NSThread: 0x1c0472e40>{number = 6, name = (null)}
剩余票数 1 卖票窗口 <NSThread: 0x1c027b540>{number = 4, name = (null)}
票已经卖完了
票已经卖完了

使用二:信号量还可以控制线程数量,例如初始化的时候,设置最多3条线程。在不使用信号量的情况下,运行一段时间就会崩溃,这是由于多线程同时操作tickets票池的removeLast去库存的方法引起的,所以我们需要考虑线程安全问题。

#import "SemaphoreDemo.h"

@interface SemaphoreDemo()

@property (strong, nonatomic) dispatch_semaphore_t semaphore;

@end

@implementation SemaphoreDemo

- (instancetype)init
{
    self = [super init];
    if (self) {
        // 创建信号量,设置每次最多三条线程执行
        self.semaphore = dispatch_semaphore_create(3);
    }
    return self;
}

// 其他演示
- (void)otherTest
{
    for (int i = 0; i < 10; I++)
    {
        [[[NSThread alloc] initWithTarget:self selector:@selector(test) object:nil] start];
    }
}

// 线程10、7、6、9、8
- (void)test
{
    // 如果信号量的值 > 0,就让信号量的值减1,然后继续往下执行代码
    // 如果信号量的值 <= 0,就会休眠等待,直到信号量的值变成>0,就让信号量的值减1,然后继续往下执行代码
    dispatch_semaphore_wait(self.semaphore, DISPATCH_TIME_FOREVER);
    
    sleep(2);
    NSLog(@"test方法 - %@", [NSThread currentThread]);
    
    // 让信号量的值+1
    dispatch_semaphore_signal(self.semaphore);
}

@end

输出结果为:

2020-07-16 17:56:26.732081+0800 多线程Demo[60970:18963768] test方法 - <NSThread: 0x600000981a80>{number = 7, name = (null)}
2020-07-16 17:56:26.732141+0800 多线程Demo[60970:18963766] test方法 - <NSThread: 0x600000981a00>{number = 5, name = (null)}
2020-07-16 17:56:26.732125+0800 多线程Demo[60970:18963767] test方法 - <NSThread: 0x600000981a40>{number = 6, name = (null)}

2020-07-16 17:56:28.732922+0800 多线程Demo[60970:18963770] test方法 - <NSThread: 0x600000981b00>{number = 9, name = (null)}
2020-07-16 17:56:28.732922+0800 多线程Demo[60970:18963769] test方法 - <NSThread: 0x600000981ac0>{number = 8, name = (null)}
2020-07-16 17:56:28.732922+0800 多线程Demo[60970:18963771] test方法 - <NSThread: 0x600000981b40>{number = 10, name = (null)}

2020-07-16 17:56:30.738444+0800 多线程Demo[60970:18963772] test方法 - <NSThread: 0x600000981b80>{number = 11, name = (null)}
2020-07-16 17:56:30.738491+0800 多线程Demo[60970:18963774] test方法 - <NSThread: 0x600000981c00>{number = 13, name = (null)}
2020-07-16 17:56:30.738492+0800 多线程Demo[60970:18963773] test方法 - <NSThread: 0x600000981bc0>{number = 12, name = (null)}

2020-07-16 17:56:32.741099+0800 多线程Demo[60970:18963775] test方法 - <NSThread: 0x600000981c40>{number = 14, name = (null)}

由结果可知,每次最多三条线程执行。

方案十一:@synchronized

@synchronized是对mutex递归锁(pthread_mutex(recursive))的封装。在创建单例对象的时候使用,保证在多线程下创建的对象是唯一的,是加锁技术中使用起来最简单的方案,但性能差。

Objective-C中,我们可以用@synchronized关键字来修饰一个对象,并为其自动加上和解除互斥锁。
但是在Swift中,没有与之对应的方法,即@synchronizedSwift中已经(或者是暂时)不存在了。其实@synchronized在幕后做的事情是调用了objc_sync中的objc_sync_enterobjc_sync_exit方法,我可以直接调用这两个方法去实现。

解决问题
#import "SynchronizedDemo.h"

@implementation SynchronizedDemo

// 存钱
- (void)saveMoney
{
    @synchronized (self) {
        [super saveMoney];
    }
}

// 取钱
- (void)drawMoney
{
    @synchronized (self) {
        [super drawMoney];
    }
}

@end

输出结果为:

2020-07-16 18:04:00.026101+0800 多线程Demo[61052:18971471] 存10元,还剩110元 - <NSThread: 0x600000011f40>{number = 5, name = (null)}
2020-07-16 18:04:00.026252+0800 多线程Demo[61052:18971471] 存10元,还剩120元 - <NSThread: 0x600000011f40>{number = 5, name = (null)}
2020-07-16 18:04:00.026354+0800 多线程Demo[61052:18971471] 存10元,还剩130元 - <NSThread: 0x600000011f40>{number = 5, name = (null)}
2020-07-16 18:04:00.026429+0800 多线程Demo[61052:18971471] 存10元,还剩140元 - <NSThread: 0x600000011f40>{number = 5, name = (null)}
2020-07-16 18:04:00.026489+0800 多线程Demo[61052:18971471] 存10元,还剩150元 - <NSThread: 0x600000011f40>{number = 5, name = (null)}
2020-07-16 18:04:00.026572+0800 多线程Demo[61052:18971468] 取20元,还剩130元 - <NSThread: 0x600000000c40>{number = 4, name = (null)}
2020-07-16 18:04:00.026651+0800 多线程Demo[61052:18971468] 取20元,还剩110元 - <NSThread: 0x600000000c40>{number = 4, name = (null)}
2020-07-16 18:04:00.026739+0800 多线程Demo[61052:18971468] 取20元,还剩90元 - <NSThread: 0x600000000c40>{number = 4, name = (null)}
2020-07-16 18:04:00.026905+0800 多线程Demo[61052:18971468] 取20元,还剩70元 - <NSThread: 0x600000000c40>{number = 4, name = (null)}
2020-07-16 18:04:00.027118+0800 多线程Demo[61052:18971468] 取20元,还剩50元 - <NSThread: 0x600000000c40>{number = 4, name = (null)}

可见,多线程的数据没有发生错乱。

买火车票问题

购买火车票,多个窗口卖票(并发),票卖出去之后要把库存减掉(同步),多个窗口出票成功(并发)。

var tickets: [Int] = [Int]()
    
@IBAction func onThread() {
    let que = DispatchQueue.init(label: "com.jk.thread", attributes: .concurrent)
    
    //生成100张票
    for i in 0..<100 {
        tickets.append(i)
    }
    
    //北京卖票窗口
    que.async {
        self.saleTicket()
    }
    
    //上海卖票窗口
    que.async {
        self.saleTicket()
    }
}

func saleTicket() {
    while true {
        //加锁,关门,执行任务
        objc_sync_enter(self)
        
        if tickets.count > 0 {
            print("剩余票数", tickets.count, "卖票窗口", Thread.current)
            tickets.removeLast()
            Thread.sleep(forTimeInterval: 0.2)
        }
        else {
            print("票已经卖完了")
            
            //开锁,开门,让其他任务可以执行
            objc_sync_exit(self)
            
            break
        }
        
        //开锁,开门,让其他任务可以执行
        objc_sync_exit(self)
    }
}

输出结果为:

剩余票数 100 卖票窗口 <NSThread: 0x1c04697c0>{number = 6, name = (null)}
剩余票数 99 卖票窗口 <NSThread: 0x1c44706c0>{number = 4, name = (null)}
剩余票数 98 卖票窗口 <NSThread: 0x1c04697c0>{number = 6, name = (null)}
剩余票数 97 卖票窗口 <NSThread: 0x1c44706c0>{number = 4, name = (null)}
剩余票数 96 卖票窗口 <NSThread: 0x1c04697c0>{number = 6, name = (null)}
.................
剩余票数 3 卖票窗口 <NSThread: 0x1c44706c0>{number = 4, name = (null)}
剩余票数 2 卖票窗口 <NSThread: 0x1c04697c0>{number = 6, name = (null)}
剩余票数 1 卖票窗口 <NSThread: 0x1c44706c0>{number = 4, name = (null)}
票已经卖完了
票已经卖完了
原理探究

锁是如何与你传入 @synchronized 的对象关联上的?
@synchronized block 在被保护的代码上暗中添加了一个异常处理。为的是同步某对象时如若抛出异常,锁会被释放掉。@synchronized block 会变成 objc_sync_enterobjc_sync_exit 的成对儿调用,我们不知道这些函数是干啥的,但基于这些事实我们可以认为编译器将会转化为这样的代码:

@synchronized(obj) {
    // do work
}

==========转化为==========

@try {
    objc_sync_enter(obj);
    // do work
} @finally {
    objc_sync_exit(obj);    
}

sychronized 的每个对象,Objective-C runtime 都会为其分配一个递归锁并存储在哈希表中。即@synchronized(obj)内部会生成obj对应的递归锁,然后进行加锁、解锁操作。

拿到需要加锁的对象,传入对象从散列表中取出对应的锁:

static StripeMap<SyncList> sDataLists// 散列表(哈希)
SyncData *data = sDataLists[obj].data
data->mutex.lock()

@synchronized会保持(retain,增加引用计数)被锁住的对象么?

NSDate *test = [NSDate date];
// This should always be `1`
NSLog(@"%@", @([test retainCount]));

@synchronized (test) {

    // This will be `2` if `@synchronized` somehow
    // retains `test`
    NSLog(@"%@", @([test retainCount]));
}

两次输出结果都是 1,那么objc_sync_enter貌似是没保持被传入的对象啊。

假如你传入 @synchronized 的对象在 @synchronized 的 block 里面被释放或者被赋值为 nil 将会怎么样?
那么你就不会得到任何锁而且你的代码将不会是线程安全的!

方案十二:atomic

修饰属性的关键字,对被修饰的对象进行原子操作(只保证赋值和读取线程安全,不保证使用)。

@property (atomic) NSMutableArray *mutableArray;

// 安全
self.mutableArray = [NSMutableArray array];

// 危险
[self.mutableArray addObject:obj];

方案十三:pthread_rwlock读写锁

多读单写:读操作可并发,写操作是互斥的。

使用说明
// 需要导入头文件
#import <pthread.h>
// 初始化锁
pthread_rwlock_t lock;
pthread_rwlock_init(&lock, NULL);
// 读-加锁
pthread_rwlock_rdlock(&lock);
// 读-尝试加锁
pthread_rwlock_tryrdlock(&lock);
// 写-加锁
pthread_rwlock_wrlock(&lock);
// 写-尝试加锁
pthread_rwlock_trywrlock(&lock);
// 解锁
pthread_rwlock_unlock(&lock);
// 销毁
pthread_rwlock_destroy(&lock);
实战演示
#import "pthread_rwlockDemo.h"
#import <pthread.h>

@interface pthread_rwlockDemo()

@property (assign, nonatomic) pthread_rwlock_t lock;

@end

@implementation pthread_rwlockDemo

- (instancetype)init
{
    self = [super init];
    if (self) {
        // 初始化锁
        pthread_rwlock_init(&_lock, NULL);
    }
    return self;
}

- (void)dealloc
{
    pthread_rwlock_destroy(&_lock);
}

// 多读
- (void)read {
    pthread_rwlock_rdlock(&_lock);
    
    sleep(1);
    NSLog(@"%s", __func__);
    
    pthread_rwlock_unlock(&_lock);
}

// 单写
- (void)write
{
    pthread_rwlock_wrlock(&_lock);
    
    sleep(1);
    NSLog(@"%s", __func__);
    
    pthread_rwlock_unlock(&_lock);
}

// 其他演示
- (void)otherTest{
    // 全局队列
    dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
    
    // 并发执行写后读
    for (int i = 0; i < 3; i++) {
        dispatch_async(queue, ^{
            [self write];
            [self read];
        });
        
    }
    
    // 并发执行读
    for (int i = 0; i < 3; i++) {
        dispatch_async(queue, ^{
            [self write];
        });
    }
}

@end

输出结果为:

2020-07-17 09:46:26.743011+0800 多线程Demo[61491:19029089] -[pthread_rwlockDemo write]
2020-07-17 09:46:27.746012+0800 多线程Demo[61491:19029088] -[pthread_rwlockDemo write]
2020-07-17 09:46:28.749845+0800 多线程Demo[61491:19029087] -[pthread_rwlockDemo write]
2020-07-17 09:46:29.755014+0800 多线程Demo[61491:19029093] -[pthread_rwlockDemo write]
2020-07-17 09:46:30.760453+0800 多线程Demo[61491:19029095] -[pthread_rwlockDemo write]
2020-07-17 09:46:31.761375+0800 多线程Demo[61491:19029090] -[pthread_rwlockDemo write]
2020-07-17 09:46:32.763769+0800 多线程Demo[61491:19029089] -[pthread_rwlockDemo read]
2020-07-17 09:46:32.763774+0800 多线程Demo[61491:19029087] -[pthread_rwlockDemo read]
2020-07-17 09:46:32.763774+0800 多线程Demo[61491:19029088] -[pthread_rwlockDemo read]

由结果可知,打印write的时候,方法每次都是一个一个执行的,而read是可以同时执行的,也就是说达到了多读单写的功能,被称为读写锁。

方案十四:dispatch_barrier_async异步栅栏

我们有时需要异步执行两组操作,而且第一组操作执行完之后,才能开始执行第二组操作。这样我们就需要一个相当于 栅栏 一样的一个方法将两组异步执行的操作组给分割起来,当然这里的操作组里可以包含一个或多个任务。这就需要用到dispatch_barrier_async方法在两个操作组间形成栅栏。

这个函数传入的并发队列必须是自己通过dispatch_queue_cretate创建的,如果传入的是一个串行或是一个全局的并发队列,那这个函数便等同于dispatch_async函数的效果。

使用说明
// 初始化队列
self.queue = dispatch_queue_create("rw_queue", DISPATCH_QUEUE_CONCURRENT);

// 读
dispatch_async(self.queue, ^{
});
        
// 写
dispatch_barrier_async(self.queue, ^{
});
实战演示
#import "dispatch_barrier_asyncDemo.h"

@interface dispatch_barrier_asyncDemo ()

@property (strong, nonatomic) dispatch_queue_t queue;

@end

@implementation dispatch_barrier_asyncDemo

// 读
- (void)read {
    sleep(1);
    NSLog(@"read");
}

// 写
- (void)write
{
    sleep(1);
    NSLog(@"write");
}

// 其他测试
- (void)otherTest{
    
    // 初始化队列
    self.queue = dispatch_queue_create("rw_queue", DISPATCH_QUEUE_CONCURRENT);
    
    for (int i = 0; i < 3; i++) {
        // 读
        dispatch_async(self.queue, ^{
            [self read];
        });
        
        // 写
        dispatch_barrier_async(self.queue, ^{
            [self write];
        });
        
         // 读
        dispatch_async(self.queue, ^{
            [self read];
        });
        
         // 读
        dispatch_async(self.queue, ^{
            [self read];
        });
        
    }
}

@end

输出结果:

2020-07-17 09:54:38.183629+0800 多线程Demo[61565:19036669] read
2020-07-17 09:54:39.184246+0800 多线程Demo[61565:19036669] write
2020-07-17 09:54:40.186081+0800 多线程Demo[61565:19036669] read
2020-07-17 09:54:40.186082+0800 多线程Demo[61565:19036668] read
2020-07-17 09:54:40.186085+0800 多线程Demo[61565:19036667] read
2020-07-17 09:54:41.189304+0800 多线程Demo[61565:19036669] write
2020-07-17 09:54:42.194755+0800 多线程Demo[61565:19036669] read
2020-07-17 09:54:42.194763+0800 多线程Demo[61565:19036667] read
2020-07-17 09:54:42.194763+0800 多线程Demo[61565:19036668] read
2020-07-17 09:54:43.200178+0800 多线程Demo[61565:19036667] write
2020-07-17 09:54:44.201968+0800 多线程Demo[61565:19036667] read
2020-07-17 09:54:44.202016+0800 多线程Demo[61565:19036668] read

遇到写的操作,就会把其他读或者写都会暂停,也就是说起到了栅栏的作用。

买火车票问题

购买火车票,多个窗口卖票(并发),票卖出去之后要把库存减掉(同步),多个窗口出票成功(并发)。

var tickets: [Int] = [Int]()

@IBAction func onThread() {
    let que = DispatchQueue.init(label: "com.jk.thread", attributes: .concurrent)
    
    //生成100张票
    for i in 0..<100 {
        tickets.append(i)
    }
    
    for _ in 0..<51 {
        //北京卖票窗口
        que.async {
            self.saleTicket()
        }
        
        //GCD 栅栏方法,同步去库存
        que.async(flags: .barrier) {
            if self.tickets.count > 0 {
                self.tickets.removeLast()
            }
        }
        
        //上海卖票窗口
        que.async {
            self.saleTicket()
        }
        
        //GCD 栅栏方法,同步去库存
        que.async(flags: .barrier) {
            if self.tickets.count > 0 {
                self.tickets.removeLast()
            }
        }
    }
}

func saleTicket() {
    if tickets.count > 0 {
        print("剩余票数", tickets.count, "卖票窗口", Thread.current)
        Thread.sleep(forTimeInterval: 0.2)
    }
    else {
        print("票已经卖完了")
    }
}

输出结果为:

剩余票数 100 卖票窗口 <NSThread: 0x1c0463c40>{number = 3, name = (null)}
剩余票数 99 卖票窗口 <NSThread: 0x1c0463c40>{number = 3, name = (null)}
剩余票数 98 卖票窗口 <NSThread: 0x1c0463c40>{number = 3, name = (null)}
剩余票数 97 卖票窗口 <NSThread: 0x1c0463c40>{number = 3, name = (null)}
.................
剩余票数 59 卖票窗口 <NSThread: 0x1c0670100>{number = 6, name = (null)}
剩余票数 58 卖票窗口 <NSThread: 0x1c0670100>{number = 6, name = (null)}
剩余票数 57 卖票窗口 <NSThread: 0x1c0670100>{number = 6, name = (null)}
剩余票数 56 卖票窗口 <NSThread: 0x1c0670100>{number = 6, name = (null)}
.................
剩余票数 3 卖票窗口 <NSThread: 0x1c0463c40>{number = 3, name = (null)}
剩余票数 2 卖票窗口 <NSThread: 0x1c0463c40>{number = 3, name = (null)}
剩余票数 1 卖票窗口 <NSThread: 0x1c0463c40>{number = 3, name = (null)}
票已经卖完了
票已经卖完了

方案十五:dispatch_group_t调度组

使用说明
//1.创建调度组
dispatch_group_t group = dispatch_group_create();
//2.队列
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
    
//3.调度组监听队列 标记开始本次执行
dispatch_group_enter(group);
//标记本次请求完成
dispatch_group_leave(group);
          
//4.调度组都完成了
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
//执行刷新UI等操作
});
实战演示
#import "dispatch_group_tDemo.h"

@implementation dispatch_group_tDemo

// 下载任务1
- (void)downLoadImage1 {
    sleep(1);
    NSLog(@"%s--%@",__func__,[NSThread currentThread]);
}

// 下载任务2
- (void)downLoadImage2 {
     sleep(2);
    NSLog(@"%s--%@",__func__,[NSThread currentThread]);
}

// 刷新UI
- (void)reloadUI
{
    NSLog(@"%s--%@",__func__,[NSThread currentThread]);
}

// 其他演示
- (void)otherTest{
    
    //1.创建调度组
    dispatch_group_t group = dispatch_group_create();
    //2.队列
    dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
    
    //3.调度组监听队列 标记开始本次执行
    dispatch_group_enter(group);
    dispatch_async(queue, ^{
       [self downLoadImage1];
       //标记本次请求完成
       dispatch_group_leave(group);
    });
    

    //3.调度组监听队列 标记开始本次执行
    dispatch_group_enter(group);
    dispatch_async(queue, ^{
        [self downLoadImage2];
        //标记本次请求完成
        dispatch_group_leave(group);
    });
    
    //4,调度组都完成了
    dispatch_group_notify(group, dispatch_get_main_queue(), ^{
        //执行完test1和test2之后,在进行请求test3
         [self reloadUI];
    });
}

@end

输出结果为:

2020-07-17 10:07:36.550557+0800 多线程Demo[61647:19046062] 下载任务1  -[dispatch_group_tDemo downLoadImage1]--<NSThread: 0x6000025aefc0>{number = 4, name = (null)}
2020-07-17 10:07:37.553169+0800 多线程Demo[61647:19046060] 下载任务2  -[dispatch_group_tDemo downLoadImage2]--<NSThread: 0x6000025d2ec0>{number = 7, name = (null)}
2020-07-17 10:07:37.553401+0800 多线程Demo[61647:19045549] 刷新UI  -[dispatch_group_tDemo reloadUI]--<NSThread: 0x6000025e8d80>{number = 1, name = main}

等两个模拟下载图片的操作都完成后,才回到主线程刷新UI。

总结

性能从高到低排序:

os_unfair_lock 自旋锁
OSSpinLock 自旋锁
dispatch_semaphore 信号量
pthread_mutex 互斥锁
dispatch_queue(DISPATCH_QUEUE_SERIAL) 串行队列
NSLock 普通(互斥)锁
NSCondition 条件锁
pthread_mutex(recursive) 递归锁
NSRecursiveLock 递归锁
NSConditionLock 条件锁
@synchronized 递归锁

四、Mach

MACH内核

  • 进程和线程抽象
  • 虚拟内存管理
  • 任务调度
  • 进程间通信和消息传递机制

BSD

  • UNIX进程模型
  • POSIX线程模型
  • UNIX用户与组
  • 网络协议栈
  • 文件系统访问
  • 设备访问

Mach的独特之处在于选择了通过消息传递的方式实现对象与对象之间的通信。

Mach消息的发送和接收都是通过同一个API函数mach_msg()进行的,这个函数在用户态和内核态都有实现。为了实现消息的发送和接收,mach_msg()函数调用了一个Mach陷阱(trap)。Mach陷阱就是Mach中和系统调用等同的概念。在用户态调用mach_msg_trap()会引发陷阱机制,切换到内核态,在内核态中,内核实现的mach_msg()会完成实际的工作。

每一个BSD进程都在底层关联一个Mach任务对象,因为Mach提供的都是非常底层的抽象,提供的API从设计上讲很基础且不完整,所以需要在这之上提供一个更高的层次以实现完整的功能。我们开发层遇到的进程和线程就是BSD层对Mach的任务和线程的复杂包装。

进程填充的是线程,而线程是二进制代码的实际执行单元。用户态的线程始于对pthread_create的调用。这个函数的又由bsdthread_create系统调用完成,而bsdthread_create又其实是Mach中的thread_create的复杂包装,说到底真正的线程创建还是有Mach层完成。

typedef struct
{
        mach_msg_header_t      header;
        mach_msg_body_t        body;
} mach_msg_base_t;

typedef struct 
{
  mach_msg_bits_t         msgh_bits; // 消息头标志位
  mach_msg_size_t         msgh_size; // 大小
  mach_port_t msgh_remote_port; // 目标(发消息)或源(接消息)
  mach_port_t msgh_local_port; // 源(发消息)或目标(接消息)
  mach_port_name_t         msgh_voucher_port;
  mach_msg_id_t msgh_id; // 唯一id
} mach_msg_header_t;

方案十六:addDependency操作依赖

NSOperationNSOperationQueue最吸引人的地方是它能添加操作之间的依赖关系。通过操作依赖,我们可以很方便的控制操作之间的执行先后顺序。

买火车票问题

购买火车票,多个窗口卖票(并发),票卖出去之后要把库存减掉(同步),多个窗口出票成功(并发)。

var tickets: [Int] = [Int]()

@IBAction func onThread() {
    let que = OperationQueue.init()//并发队列
    que.maxConcurrentOperationCount = 1
    
    //生成100张票
    for i in 0..<100 {
        tickets.append(i)
    }
    
    for _ in 0..<51 {
        //addDependency方法,同步去库存
        let sync1 = BlockOperation.init(block: {
            if self.tickets.count > 0 {
                self.tickets.removeLast()
            }
        })
        
        //北京卖票窗口
        let bj = BlockOperation.init(block: {
            self.saleTicket()
        })
        bj.addDependency(sync1)//等待去库存
        
        //addDependency方法,同步去库存
        let sync2 = BlockOperation.init(block: {
            if self.tickets.count > 0 {
                self.tickets.removeLast()
            }
        })
        
        //上海卖票窗口
        let sh = BlockOperation.init(block: {
            self.saleTicket()
        })
        sh.addDependency(sync2)//等待去库存
        
        que.addOperation(sync1)
        que.addOperation(bj)
        que.addOperation(sync2)
        que.addOperation(sh)
    }
}

func saleTicket() {
    if tickets.count > 0 {
        print("剩余票数", tickets.count, "卖票窗口", Thread.current)
        Thread.sleep(forTimeInterval: 0.2)
    }
    else {
        print("票已经卖完了")
    }
}

输出结果为:

剩余票数 99 卖票窗口 <NSThread: 0x1c42672c0>{number = 4, name = (null)}
剩余票数 98 卖票窗口 <NSThread: 0x1c06731c0>{number = 5, name = (null)}
剩余票数 97 卖票窗口 <NSThread: 0x1c06731c0>{number = 5, name = (null)}
剩余票数 96 卖票窗口 <NSThread: 0x1c06731c0>{number = 5, name = (null)
.................
剩余票数 54 卖票窗口 <NSThread: 0x1c42672c0>{number = 4, name = (null)}
剩余票数 53 卖票窗口 <NSThread: 0x1c42672c0>{number = 4, name = (null)}
剩余票数 52 卖票窗口 <NSThread: 0x1c42672c0>{number = 4, name = (null)}
.................
剩余票数 2 卖票窗口 <NSThread: 0x1c42672c0>{number = 4, name = (null)}
剩余票数 1 卖票窗口 <NSThread: 0x1c42672c0>{number = 4, name = (null)}
票已经卖完了
票已经卖完了
票已经卖完了

Demo

Demo在我的Github上,欢迎下载。
MultiThreadDemo

参考文章

iOS 多线程:『GCD』详尽总结
iOS 多线程:『NSOperation、NSOperationQueue』详尽总结
GCD源码分析
iOS中使用到的加锁方案
关于 @synchronized,这儿比你想知道的还要多

相关文章

网友评论

      本文标题:IOS基础:多线程(下)

      本文链接:https://www.haomeiwen.com/subject/fytvmktx.html