iOS - 多线程的安全隐患

文章目录

  • [iOS - 多线程的安全隐患](#iOS - 多线程的安全隐患)
    • [1. 卖票案例](#1. 卖票案例)
    • [2. 多线程安全隐患的解决方案](#2. 多线程安全隐患的解决方案)
      • [2.1 iOS中的线程同步方案](#2.1 iOS中的线程同步方案)
      • [2.2 同步方案的使用](#2.2 同步方案的使用)
        • [2.2.1 OSSpinLock](#2.2.1 OSSpinLock)
          • [2.2.1.1 使用方法:](#2.2.1.1 使用方法:)
          • [2.2.1.2 案例](#2.2.1.2 案例)
        • [2.2.2 os_unfair_lock](#2.2.2 os_unfair_lock)
          • [2.2.2.1 使用方法:](#2.2.2.1 使用方法:)
          • [2.2.2.2 案例](#2.2.2.2 案例)
        • [2.2.3 pthread_mutex](#2.2.3 pthread_mutex)
          • [2.2.3.1 使用方法](#2.2.3.1 使用方法)
          • [2.2.3.2 案例](#2.2.3.2 案例)
          • [2.2.3.3 pthread_mutex-递归锁](#2.2.3.3 pthread_mutex-递归锁)
          • [2.2.3.4 pthread_mutex-条件锁](#2.2.3.4 pthread_mutex-条件锁)
            • [2.2.3.4.1 使用方法](#2.2.3.4.1 使用方法)
            • [2.2.3.4.2 案例](#2.2.3.4.2 案例)
        • [2.2.4 dispatch_semaphore](#2.2.4 dispatch_semaphore)
        • [2.2.5 dispatch_queue(DISPATCH_QUEUE_SERIAL)](#2.2.5 dispatch_queue(DISPATCH_QUEUE_SERIAL))
        • [2.2.6 NSLock、NSRecursiveLock](#2.2.6 NSLock、NSRecursiveLock)
          • [2.2.6.1 NSLock是对mutex普通锁的封装](#2.2.6.1 NSLock是对mutex普通锁的封装)
            • [2.2.6.1.1 使用方式](#2.2.6.1.1 使用方式)
          • [2.2.6.2 NSRecursiveLock也是对mutex递归锁的封装,API跟NSLock基本一致](#2.2.6.2 NSRecursiveLock也是对mutex递归锁的封装,API跟NSLock基本一致)
          • [2.2.6.3 案例](#2.2.6.3 案例)
            • [2.2.6.3.1 NSLock](#2.2.6.3.1 NSLock)
            • [2.2.6.3.2 NSRecursiveLock](#2.2.6.3.2 NSRecursiveLock)
        • [2.2.7 NSCondition](#2.2.7 NSCondition)
          • [2.2.7.1 案例](#2.2.7.1 案例)
        • [2.2.8 NSConditionLock](#2.2.8 NSConditionLock)
          • [2.2.8.1 案例](#2.2.8.1 案例)
        • [2.2.9 dispatch_queue - 串行队列](#2.2.9 dispatch_queue - 串行队列)
          • [2.2.9.1 案例](#2.2.9.1 案例)
        • [2.2.10 dispatch_semaphore - 信号量](#2.2.10 dispatch_semaphore - 信号量)
          • [2.2.10.1 控制最大并发数](#2.2.10.1 控制最大并发数)
          • [2.2.10.2 保证线程同步](#2.2.10.2 保证线程同步)
          • [2.2.10.2.1 使用信号量可能会造成线程优先级反转,且无法避免](#2.2.10.2.1 使用信号量可能会造成线程优先级反转,且无法避免)
        • [2.2.11 @synchronized](#2.2.11 @synchronized)
        • [2.2.11.1 底层原理](#2.2.11.1 底层原理)
        • [2.2.11.1 案例](#2.2.11.1 案例)
    • [3. 拓展](#3. 拓展)
      • [3.1 iOS线程同步方案性能比较](#3.1 iOS线程同步方案性能比较)
      • [3.2 自旋锁、互斥锁比较](#3.2 自旋锁、互斥锁比较)
        • [3.2.1 什么情况使用自旋锁比较划算?](#3.2.1 什么情况使用自旋锁比较划算?)
        • [3.2.2 什么情况使用互斥锁比较划算?](#3.2.2 什么情况使用互斥锁比较划算?)

iOS - 多线程的安全隐患

  • 资源共享

    • 1块资源可能会被多个线程共享,也就是多个线程可能会访问同一块资源
    • 比如多个线程访问同一个对象、同一个变量、同一个文件
  • 当多个线程访问同一块资源时,很容易引发数据错乱数据安全问题

1. 卖票案例

假设总共15张票,使用3个异步线程并发执行,每个线程卖5张票,观察最后的打印结果

复制代码
@interface ViewController ()

@property (nonatomic, assign) int ticketsCount;

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view.
}

- (void)saleTicket {
    int oldTicketsCount = self.ticketsCount;
    sleep(0.2);
    oldTicketsCount--;
    self.ticketsCount = oldTicketsCount;
    
    NSLog(@"还剩 %d 张票", oldTicketsCount);
}

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    self.ticketsCount = 15;
    dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
    dispatch_async(queue, ^{
        for (int i = 0; i < 5; i++) {
            [self saleTicket];
        }
    });
    
    dispatch_async(queue, ^{
        for (int i = 0; i < 5; i++) {
            [self saleTicket];
        }
    });
    
    dispatch_async(queue, ^{
        for (int i = 0; i < 5; i++) {
            [self saleTicket];
        }
    });
}

@end

出现票重复卖票,最后没卖完的情况

时序图示意:

分析图:

2. 多线程安全隐患的解决方案

  • 解决方案:使用线程同步技术(同步,就是协同步调,按预定的先后次序进行)
  • 常见的线程同步技术是:加锁

2.1 iOS中的线程同步方案

  • OSSpinLock
  • os_unfair_lock
  • pthread_mutex
  • dispatch_semaphore
  • dispatch_queue(DISPATCH_QUEUE_SERIAL)
  • NSLock
  • NSRecursiveLock
  • NSCondition
  • NSConditionLock
  • @synchronized

2.2 同步方案的使用

为了方便调试,我们做个简单封装,将需要加锁的代码放在ZSXBaseDemo中,同时暴漏方法给子类重写:

    • (void)__saveMoney;
    • (void)__drawMoney;
    • (void)__saleTicket;

每一把锁将创建一个ZSXBaseDemo子类,然后重写上述方法,实现各自的加锁方式

ZSXBaseDemo.h

复制代码
@interface ZSXBaseDemo : NSObject

- (void)moneyTest;
- (void)ticketTest;
- (void)otherTest;

#pragma mark - 暴露给子类去使用
- (void)__saveMoney;
- (void)__drawMoney;
- (void)__saleTicket;

@end

ZSXBaseDemo.m

复制代码
@interface ZSXBaseDemo()

@property (assign, nonatomic) int money;
@property (assign, nonatomic) int ticketsCount;

@end

@implementation ZSXBaseDemo

- (void)otherTest {}

/**
 存钱、取钱演示
 */
- (void)moneyTest
{
    self.money = 100;
    
    dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
    
    dispatch_async(queue, ^{
        for (int i = 0; i < 10; i++) {
            [self __saveMoney];
        }
    });
    
    dispatch_async(queue, ^{
        for (int i = 0; i < 10; i++) {
            [self __drawMoney];
        }
    });
}

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

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

/**
 卖1张票
 */
- (void)__saleTicket
{
    int oldTicketsCount = self.ticketsCount;
    sleep(.2);
    oldTicketsCount--;
    self.ticketsCount = oldTicketsCount;
    NSLog(@"还剩%d张票 - %@", oldTicketsCount, [NSThread currentThread]);
}

/**
 卖票演示
 */
- (void)ticketTest
{
    self.ticketsCount = 15;
    
    dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
    
//    for (int i = 0; i < 10; i++) {
//        [[[NSThread alloc] initWithTarget:self selector:@selector(__saleTicket) object:nil] start];
//    }
    
    dispatch_async(queue, ^{
        for (int i = 0; i < 5; i++) {
            [self __saleTicket];
        }
    });
    
    dispatch_async(queue, ^{
        for (int i = 0; i < 5; i++) {
            [self __saleTicket];
        }
    });
    
    dispatch_async(queue, ^{
        for (int i = 0; i < 5; i++) {
            [self __saleTicket];
        }
    });
}

@end
2.2.1 OSSpinLock
  • OSSpinLock叫做"自旋锁",等待锁的线程会处于忙等(busy-wait)状态,一直占用着CPU资源
  • 目前已经不再安全,可能会出现优先级反转问题
    • 如果等待锁的线程优先级较高,它会一直占用着CPU资源,优先级低的线程就无法释放锁
    • 需要导入头文件#import <libkern/OSAtomic.h>
2.2.1.1 使用方法:
复制代码
// 初始化
OSSpinLock lock = OS_SPINLOCK_INIT;
    
// 尝试加锁
bool result =  OSSpinLockTry(&lock);
    
// 加锁
OSSpinLockLock(&lock);
    
// 解锁
OSSpinLockUnlock(&lock);
2.2.1.2 案例
复制代码
#import "OSSpinLockDemo.h"
#import <libkern/OSAtomic.h>

@interface OSSpinLockDemo ()

// High-level lock
// 自旋锁,有优先级反转问题
@property (nonatomic, assign) OSSpinLock moneyLock;
@property (nonatomic, assign) OSSpinLock ticketLock;

@end

@implementation OSSpinLockDemo

- (instancetype)init {
    if (self = [super init]) {
        _moneyLock = OS_SPINLOCK_INIT;
        _ticketLock = OS_SPINLOCK_INIT;
        
        //使用方法
        // 初始化
        OSSpinLock lock = OS_SPINLOCK_INIT;
        
        // 尝试加锁
        bool result =  OSSpinLockTry(&lock);
        
        // 加锁
        OSSpinLockLock(&lock);
        
        // 解锁
        OSSpinLockUnlock(&lock);
    }
    return self;
}

#pragma mark reload
- (void)__drawMoney {
    OSSpinLockLock(&_moneyLock);
    
    [super __drawMoney];
    
    OSSpinLockUnlock(&_moneyLock);
}

- (void)__saveMoney {
    OSSpinLockLock(&_moneyLock);
    
    [super __saveMoney];
    
    OSSpinLockUnlock(&_moneyLock);
}

- (void)__saleTicket {
    OSSpinLockLock(&_ticketLock);
    
    [super __saleTicket];
    
    OSSpinLockUnlock(&_ticketLock);
}
#pragma mark end

@end

对于卖票,只有__saleTicket方法中需要使用,因此也可以使用static关键字创建静态变量,无需声明为属性

复制代码
- (void)__saleTicket {
    static OSSpinLock ticketLock = OS_SPINLOCK_INIT;
    OSSpinLockLock(&ticketLock);
    
    [super __saleTicket];
    
    OSSpinLockUnlock(&ticketLock);
}
2.2.2 os_unfair_lock
  • os_unfair_lock用于取代不安全OSSpinLock ,从iOS10开始才支持
  • 从底层调用看,等待os_unfair_lock锁的线程会处于休眠状态,并非忙等
  • 需要导入头文件#import <os/lock.h>
2.2.2.1 使用方法:
复制代码
// 初始化
os_unfair_lock lock = OS_UNFAIR_LOCK_INIT;
    
// 尝试加锁
bool result =  os_unfair_lock_trylock(&lock);
    
// 加锁
os_unfair_lock_lock(&lock);
    
// 解锁
os_unfair_lock_unlock(&lock);
2.2.2.2 案例
复制代码
#import "OSUnfairLockDemo.h"
#import <os/lock.h>

@interface OSUnfairLockDemo()
// Low-level lock
// ll lock
// lll
// Low-level lock的特点等不到锁就休眠
@property (assign, nonatomic) os_unfair_lock moneyLock;
@property (assign, nonatomic) os_unfair_lock ticketLock;
@end

@implementation OSUnfairLockDemo

- (instancetype)init {
    if (self = [super init]) {
        _moneyLock = OS_UNFAIR_LOCK_INIT;
        _ticketLock = OS_UNFAIR_LOCK_INIT;
        
        //使用方法
        // 初始化
        os_unfair_lock lock = OS_UNFAIR_LOCK_INIT;
        
        // 尝试加锁
        bool result =  os_unfair_lock_trylock(&lock);
        
        // 加锁
        os_unfair_lock_lock(&lock);
        
        // 解锁
        os_unfair_lock_unlock(&lock);
    }
    return self;
}

#pragma mark reload
- (void)__drawMoney {
    os_unfair_lock_lock(&_moneyLock);
    
    [super __drawMoney];
    
    os_unfair_lock_unlock(&_moneyLock);
}

- (void)__saveMoney {
    os_unfair_lock_lock(&_moneyLock);
    
    [super __saveMoney];
    
    os_unfair_lock_unlock(&_moneyLock);
}

- (void)__saleTicket {
    os_unfair_lock_lock(&_ticketLock);
    
    [super __saleTicket];
    
    os_unfair_lock_unlock(&_ticketLock);
}
#pragma mark end

@end
2.2.3 pthread_mutex
  • mutex叫做"互斥锁",等待锁的线程会处于休眠状态
  • 需要导入头文件#import <pthread.h>
2.2.3.1 使用方法
复制代码
// 初始化锁
pthread_mutex_init(mutex, NULL); //相当于设置属性"PTHREAD_MUTEX_DEFAULT"

//初始化属性并设置属性
pthread_mutexattr_t attr;
pthread_mutexattr_init(&attr);
pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_DEFAULT);

//初始化锁
pthread_mutex_init(mutex, &attr);

//加锁
pthread_mutex_lock(&_mutex);

//尝试加锁
bool result = pthread_mutex_trylock(&_mutex);

//解锁
pthread_mutex_unlock(&_mutex);

//销毁相关资源
pthread_mutexattr_destroy(&attr); //销毁属性
pthread_mutex_destroy(&_mutex); //销毁锁
2.2.3.2 案例
复制代码
@interface MutexDemo ()

@property (assign, nonatomic) pthread_mutex_t ticketMutex;
@property (assign, nonatomic) pthread_mutex_t moneyMutex;

@end

@implementation MutexDemo

- (void)__initMutex:(pthread_mutex_t *)mutex {
    // 初始化锁
    pthread_mutex_init(mutex, NULL);
}

- (instancetype)init {
    if (self = [super init]) {
        [self __initMutex:&_ticketMutex];
        [self __initMutex:&_moneyMutex];
    }
    return self;
}

#pragma mark reload
- (void)__drawMoney {
    pthread_mutex_lock(&_moneyMutex);
    
    [super __drawMoney];
    
    pthread_mutex_unlock(&_moneyMutex);
}

- (void)__saveMoney {
    pthread_mutex_lock(&_moneyMutex);
    
    [super __saveMoney];
    
    pthread_mutex_unlock(&_moneyMutex);
}

- (void)__saleTicket {
    pthread_mutex_lock(&_ticketMutex);
    
    [super __saleTicket];
    
    pthread_mutex_unlock(&_ticketMutex);
}
#pragma mark end

- (void)dealloc {
    pthread_mutex_destroy(&_moneyMutex);
    pthread_mutex_destroy(&_ticketMutex);
}

@end
2.2.3.3 pthread_mutex-递归锁

递归:允许同一个线程一把锁重复加锁

otherTest方法中,假设该方法中已经加锁,同时会调用另一个也需要加锁的方法

复制代码
- (void)otherTest {
    pthread_mutex_lock(&_mutex);
    
    NSLog(@"%s", __func__);
    
    [self otherTest2];
    
    pthread_mutex_unlock(&_mutex);
}

- (void)otherTest2 {
    pthread_mutex_lock(&_mutex);
    
    NSLog(@"%s", __func__);
    
    pthread_mutex_unlock(&_mutex);
}

此时执行代码

代码只会执行到[self otherTest2];。此时出现死锁,是因为otherTest方法加锁后,调用otherTest2otherTest2方法开始执行时也会加锁,此时因为otherTest方法还未解锁otherTest2则进入等待解锁状态,而otherTest需要等待otherTest2方法执行完才继续,所以产生死锁

使用pthread_mutexattr_t配置该锁为递归锁
PTHREAD_MUTEX_RECURSIVE

复制代码
// 初始化锁
//    pthread_mutex_init(mutex, NULL);
    
//初始化属性
pthread_mutexattr_t attr;
pthread_mutexattr_init(&attr);
pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE);
//初始化锁
pthread_mutex_init(mutex, &attr);
//销毁属性
pthread_mutexattr_destroy(&attr);

此时可以正常执行完otherTestotherTest2方法

如果otherTest方法里面是递归调用otherTest自身

复制代码
- (void)otherTest {
    pthread_mutex_lock(&_mutex);
    
    NSLog(@"%s", __func__);
    
    [self otherTest];
    
    pthread_mutex_unlock(&_mutex);
}

这时候会死循环调用otherTest

若增加一个计数,即可控制递归调用的次数

复制代码
- (void)otherTest {
    pthread_mutex_lock(&_mutex);
    
    NSLog(@"%s", __func__);
    
    static int count = 0;
    if (count < 5) {
        count++;
        [self otherTest];
    }
    
    pthread_mutex_unlock(&_mutex);
}

执行结果:

2.2.3.4 pthread_mutex-条件锁

业务场景中,可能需要不同线程间的依赖关系,比如线程1需要等待线程2执行完才能继续执行

假设有如下代码:

复制代码
- (void)otherTest {
    [[[NSThread alloc] initWithTarget:self selector:@selector(__remove) object:nil] start];
    
    [[[NSThread alloc] initWithTarget:self selector:@selector(__add) object:nil] start];
}

- (void)__remove {
    NSLog(@"%s", __func__);
    pthread_mutex_lock(&_mutex);
    
    [self.data removeLastObject];
    NSLog(@"删除一个元素");
    
    pthread_mutex_unlock(&_mutex);
}

- (void)__add {
    NSLog(@"%s", __func__);
    pthread_mutex_lock(&_mutex);
    
    [self.data addObject:@"test"];
    NSLog(@"添加一个元素");
    
    pthread_mutex_unlock(&_mutex);
}

使用多线程调用__remove__add,两者执行顺序不确定。但是希望__remove是在__add之后执行,保证先加、再减。

这时候可以使用条件锁来实现

2.2.3.4.1 使用方法
复制代码
// 初始化锁
pthread_t mutex;
pthread_mutex_init(&mutex, NULL);
// 初始化条件
pthread_cond_t condition;
pthread_cond_init(&condition, NULL);
// 等待条件(进入休眠,放开mutex 锁;被唤醒后,会再次对mutex加锁)
pthread_cond_wait(&condition, &mutex);
// 激活一个等待条件的线程
pthread_cond_signal(&condition);
// 激活所有等待条件的线程
pthread_cond_broadcast(&condition);
// 销毁资源
pthread_mutex_destroy(&mutex);
pthread_cond_destroy(&condition);
2.2.3.4.2 案例
复制代码
#import "MutexDemo3.h"
#import <pthread.h>

@interface MutexDemo3 ()

@property (nonatomic, strong) NSMutableArray *data;
@property (assign, nonatomic) pthread_mutex_t mutex;
@property (nonatomic, assign) pthread_cond_t cond;

@end

@implementation MutexDemo3

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

- (void)otherTest {
    [[[NSThread alloc] initWithTarget:self selector:@selector(__remove) object:nil] start];
    
    [[[NSThread alloc] initWithTarget:self selector:@selector(__add) object:nil] start];
}

- (void)__remove {
    NSLog(@"%s", __func__);
    pthread_mutex_lock(&_mutex);
    if (self.data.count == 0) {
        //等待条件(进入休眠,放开mutex 锁;被唤醒后,会再次对mutex加锁)
        pthread_cond_wait(&_cond, &_mutex);
    }
    [self.data removeLastObject];
    NSLog(@"删除一个元素");
    
    pthread_mutex_unlock(&_mutex);
}

- (void)__add {
    NSLog(@"%s", __func__);
    pthread_mutex_lock(&_mutex);
    
    [self.data addObject:@"test"];
    NSLog(@"添加一个元素");
    
    //激活等待条件
    pthread_cond_signal(&_cond);
    
    pthread_mutex_unlock(&_mutex);
}

- (void)dealloc {
    pthread_mutex_destroy(&_mutex);
    pthread_cond_destroy(&_cond);
}

@end

执行结果:

可以保证先添加,再删除

2.2.4 dispatch_semaphore
2.2.5 dispatch_queue(DISPATCH_QUEUE_SERIAL)
2.2.6 NSLock、NSRecursiveLock
2.2.6.1 NSLock是对mutex普通锁的封装
2.2.6.1.1 使用方式
2.2.6.2 NSRecursiveLock也是对mutex递归锁的封装,API跟NSLock基本一致
2.2.6.3 案例
2.2.6.3.1 NSLock
复制代码
#import "NSLockDemo.h"

@interface NSLockDemo()
@property (strong, nonatomic) NSLock *ticketLock;
@property (strong, nonatomic) NSLock *moneyLock;
@end

@implementation NSLockDemo


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

- (void)__saleTicket
{
    [self.ticketLock lock];
    
    [super __saleTicket];
    
    [self.ticketLock unlock];
}

- (void)__saveMoney
{
    [self.moneyLock lock];
    
    [super __saveMoney];
    
    [self.moneyLock unlock];
}

- (void)__drawMoney
{
    [self.moneyLock lock];
    
    [super __drawMoney];
    
    [self.moneyLock unlock];
}

@end
2.2.6.3.2 NSRecursiveLock
复制代码
#import "NSLockDemo2.h"

@interface NSLockDemo2()

@property (strong, nonatomic) NSRecursiveLock *recursiveLock;

@end

@implementation NSLockDemo2


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

- (void)otherTest {
    [self.recursiveLock lock];
    
    NSLog(@"%s", __func__);
    
    static int count = 0;
    if (count < 5) {
        count++;
        [self otherTest];
    }
    
    [self.recursiveLock unlock];
}

- (void)otherTest2 {
    [self.recursiveLock lock];
    
    NSLog(@"%s", __func__);
    
    [self.recursiveLock unlock];
}

@end

执行结果:

2.2.7 NSCondition
  • NSCondition是对mutex和cond的封装
2.2.7.1 案例
复制代码
#import "NSConditionDemo.h"

@interface NSConditionDemo()

@property (strong, nonatomic) NSCondition *condition;
@property (strong, nonatomic) NSMutableArray *data;

@end

@implementation NSConditionDemo

- (instancetype)init
{
    if (self = [super init]) {
        self.condition = [[NSCondition alloc] init];
        self.data = [NSMutableArray array];
    }
    return self;
}

- (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];
    NSLog(@"__remove - begin");
    
    if (self.data.count == 0) {
        // 等待
        [self.condition wait];
    }
    
    [self.data removeLastObject];
    NSLog(@"删除了元素");
    
    [self.condition unlock];
}

// 线程2
// 往数组中添加元素
- (void)__add
{
    [self.condition lock];
    
    sleep(1);
    
    [self.data addObject:@"Test"];
    NSLog(@"添加了元素");
    // 信号
    [self.condition signal];
    
    // 广播
//    [self.condition broadcast];
    [self.condition unlock];
    
}
@end

执行结果:

2.2.8 NSConditionLock
  • NSConditionLock是对NSCondition的进一步封装,可以设置具体的条件值
2.2.8.1 案例
复制代码
#import "NSConditionLockDemo.h"

@interface NSConditionLockDemo()

@property (strong, nonatomic) NSConditionLock *conditionLock;

@end

@implementation NSConditionLockDemo

- (instancetype)init
{
    if (self = [super init]) {
        self.conditionLock = [[NSConditionLock alloc] initWithCondition:1];
    }
    return self;
}

- (void)otherTest
{
    [[[NSThread alloc] initWithTarget:self selector:@selector(__one) object:nil] start];
    
    [[[NSThread alloc] initWithTarget:self selector:@selector(__two) object:nil] start];
    
    [[[NSThread alloc] initWithTarget:self selector:@selector(__three) object:nil] start];
}

- (void)__one {
    [self.conditionLock lockWhenCondition:1];
    
    NSLog(@"%s", __func__);
    sleep(1);
    
    [self.conditionLock unlockWithCondition:2];
}

- (void)__two {
    [self.conditionLock lockWhenCondition:2];
    
    NSLog(@"%s", __func__);
    sleep(1);
    
    [self.conditionLock unlockWithCondition:3];
}

- (void)__three {
    [self.conditionLock lockWhenCondition:3];
    
    NSLog(@"%s", __func__);
    
    [self.conditionLock unlock];
}

@end

执行结果:

通过设置Condition,可以实现按想要的顺序执行任务,或者说任务之间的依赖关系

condition默认值是0

即:

使用[[NSConditionLock alloc] init]初始化,condition为 0
使用- (void)lock代表直接加锁

即:
[self.conditionLock lock]

2.2.9 dispatch_queue - 串行队列
  • 直接使用GCD串行队列,也是可以实现线程同步的
2.2.9.1 案例
复制代码
#import "SerialQueueDemo.h"

@interface SerialQueueDemo()

@property (nonatomic, strong) dispatch_queue_t ticketQueue;
@property (nonatomic, strong) dispatch_queue_t moneyQueue;

@end

@implementation SerialQueueDemo

- (instancetype)init {
    if (self = [super init]) {
        self.ticketQueue = dispatch_queue_create("ticketQueue", DISPATCH_QUEUE_SERIAL);
        self.moneyQueue = dispatch_queue_create("moneyQueue", DISPATCH_QUEUE_SERIAL);
    }
    return self;
}

- (void)__saveMoney {
    dispatch_sync(self.moneyQueue, ^{
        [super __saveMoney];
    });
}

- (void)__drawMoney {
    dispatch_sync(self.moneyQueue, ^{
        [super __drawMoney];
    });
}

- (void)__saleTicket {
    dispatch_sync(self.ticketQueue, ^{
        [super __saleTicket];
    });
}

@end

执行结果:

2.2.10 dispatch_semaphore - 信号量
  • semaphore叫做"信号量"
  • 信号量的初始值,可以用来控制线程并发访问最大数量
  • 信号量的初始值为1,代表同时只允许1条线程访问资源,保证线程同步
2.2.10.1 控制最大并发数

- (void)otherTest方法循环创建20个线程执行- (void)test方法,semaphore初始值设置为5

复制代码
#import "SemaphoreDemo.h"

@interface SemaphoreDemo()

@property (nonatomic, strong) dispatch_semaphore_t semaphore;

@end

@implementation SemaphoreDemo

- (instancetype)init {
    if (self = [super init]) {
        self.semaphore = dispatch_semaphore_create(5);
    }
    return self;
}

- (void)otherTest {
    for (int i = 0; i < 20; i++) {
        [[[NSThread alloc] initWithTarget:self selector:@selector(test) object:nil] start];
    }
}

- (void)test {
    dispatch_semaphore_wait(self.semaphore, DISPATCH_TIME_FOREVER);
    
    sleep(2);
    NSLog(@"test - %@", [NSThread currentThread]);
    
    dispatch_semaphore_signal(self.semaphore);
}

@end

运行结果:

实现了控制最大并发数5

2.2.10.2 保证线程同步
复制代码
@interface SemaphoreDemo()

@property (nonatomic, strong) dispatch_semaphore_t semaphore;
@property (nonatomic, strong) dispatch_semaphore_t tacketSemaphore;
@property (nonatomic, strong) dispatch_semaphore_t moneySemaphore;

@end

@implementation SemaphoreDemo

- (instancetype)init {
    if (self = [super init]) {
        self.semaphore = dispatch_semaphore_create(5);
        self.tacketSemaphore = dispatch_semaphore_create(1);
        self.moneySemaphore = dispatch_semaphore_create(1);
    }
    return self;
}

- (void)otherTest {
    for (int i = 0; i < 20; i++) {
        [[[NSThread alloc] initWithTarget:self selector:@selector(test) object:nil] start];
    }
}

- (void)test {
    dispatch_semaphore_wait(self.semaphore, DISPATCH_TIME_FOREVER);
    
    sleep(2);
    NSLog(@"test - %@", [NSThread currentThread]);
    
    dispatch_semaphore_signal(self.semaphore);
}


- (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);
}

- (void)__saleTicket {
    dispatch_semaphore_wait(self.tacketSemaphore, DISPATCH_TIME_FOREVER);
    
    [super __saleTicket];
    
    dispatch_semaphore_signal(self.tacketSemaphore);
}

@end

执行结果:

虽然打印结果已经保证线程同步,但是窗口收到了警告

2.2.10.2.1 使用信号量可能会造成线程优先级反转,且无法避免

QoS (Quality of Service),用来指示某任务或者队列的运行优先级

  1. 记录了持有者的api都可以自动避免优先级反转,系统会通过提高相关线程的优先级来解决优先级反转的问题,如 dispatch_sync, 如果系统不知道持有者所在的线程,则无法知道应该提高谁的优先级,也就无法解决反转问题。

  2. 慎用dispatch_semaphore做线程同步

dispatch_semaphore容易造成优先级反转,因为api没有记录是哪个线程持有了信号量,所以有高优先级的线程在等待锁的时候,内核无法知道该提高那个线程的优先级(QoS);

  1. dispatch_semaphore不能避免优先级反转的原因

在调用dispatch_semaphore_wait()的时候,系统不知道哪个线程会调用 dispatch_semaphore_signal()方法,系统无法知道owner信息,无法调整优先级。dispatch_groupsemaphore类似,在调用enter()方法的时候,无法预知谁会leave(),所以系统也不知道owner信息

2.2.11 @synchronized
  • @synchronized是对mutex递归锁的封装
  • 源码查看:objc4中的objc-sync.mm文件
  • @synchronized(obj)内部会生成obj对应的递归锁,然后进行加锁、解锁操作
2.2.11.1 底层原理

使用哈希表结构,将穿进去的obj作为key,找到低层封装mutex的锁,再进行加锁、解锁操作

@synchronized (obj):obj如果相同,则代表使用同一把锁

2.2.11.1 案例
复制代码
#import "SynchronizedDemo.h"

@implementation SynchronizedDemo

- (void)__saveMoney {
    // 取钱、存钱 共用一把锁
    @synchronized (self) {
        [super __saveMoney];
    }
}

- (void)__drawMoney {
    @synchronized (self) {
        [super __drawMoney];
    }
}

- (void)__saleTicket {
    // 买票 - 单独创建一把锁
    static NSObject *lock;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        lock = [[NSObject alloc] init];
    });
    @synchronized (lock) {
        [super __saleTicket];
    }
}

@end

3. 拓展

3.1 iOS线程同步方案性能比较

性能从高到低排序

  • os_unfair_lock
  • OSSpinLock
  • dispatch_semaphore
  • pthread_mutex
  • dispatch_queue(DISPATCH_QUEUE_SERIAL)
  • NSLock
  • NSCondition
  • pthread_mutex(recursive)
  • NSRecursiveLock
  • NSConditionLock
  • @synchronized

性能排行仅供参考,不同环境实际效果可能不一样

3.2 自旋锁、互斥锁比较

3.2.1 什么情况使用自旋锁比较划算?
  • 预计线程等待锁的时间很短
  • 加锁的代码(临界区)经常被调用,但竞争情况很少发生
  • CPU资源不紧张
  • 多核处理器
3.2.2 什么情况使用互斥锁比较划算?
  • 预计线程等待锁的时间较长
  • 单核处理器
  • 临界区有IO操作
  • 临界区代码复杂或者循环量大
  • 临界区竞争非常激烈

@oubijiexi

相关推荐
鸿蒙布道师1 小时前
鸿蒙NEXT开发图片相关工具类(ArkTs)
android·ios·华为·harmonyos·arkts·鸿蒙系统·huawei
Unlimitedz16 小时前
iOS内存管理中的强引用问题
macos·ios·cocoa
雨夜赶路人16 小时前
iOS开发--接入ADMob广告失败
ios
旭日猎鹰18 小时前
iOS崩溃堆栈分析
ios
SY.ZHOU18 小时前
Flutter 与原生通信
android·flutter·ios
鸿蒙布道师21 小时前
鸿蒙NEXT开发文件预览工具类(ArkTs)
android·ios·华为·harmonyos·arkts·鸿蒙系统·huawei
鸿蒙布道师21 小时前
鸿蒙NEXT开发全局上下文管理类(ArkTs)
android·ios·华为·harmonyos·arkts·鸿蒙系统·huawei
归辞...1 天前
【iOS】OC高级编程 iOS多线程与内存管理阅读笔记——自动引用计数(二)
笔记·ios·cocoa
码客前端1 天前
ios接入穿山甲【Swift】
macos·ios·cocoa
键盘敲没电1 天前
【iOS】UITableView性能优化
ios·性能优化·ipad