iOS - 多线程的安全隐患

文章目录

  • iOS - 多线程的安全隐患
    • 1. 卖票案例
    • 2. 多线程安全隐患的解决方案
      • 2.1 iOS中的线程同步方案
      • 2.2 同步方案的使用
        • 2.2.1 OSSpinLock
          • 2.2.1.1 使用方法:
          • 2.2.1.2 案例
        • 2.2.2 os_unfair_lock
          • 2.2.2.1 使用方法:
          • 2.2.2.2 案例
        • 2.2.3 pthread_mutex
          • 2.2.3.1 使用方法
          • 2.2.3.2 案例
          • 2.2.3.3 pthread_mutex-递归锁
          • 2.2.3.4 pthread_mutex-条件锁
            • 2.2.3.4.1 使用方法
            • 2.2.3.4.2 案例
        • 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
            • 2.2.6.3.2 NSRecursiveLock
        • 2.2.7 NSCondition
          • 2.2.7.1 案例
        • 2.2.8 NSConditionLock
          • 2.2.8.1 案例
        • 2.2.9 dispatch_queue - 串行队列
          • 2.2.9.1 案例
        • 2.2.10 dispatch_semaphore - 信号量
          • 2.2.10.1 控制最大并发数
          • 2.2.10.2 保证线程同步
          • 2.2.10.2.1 使用信号量可能会造成线程优先级反转,且无法避免
        • 2.2.11 @synchronized
        • 2.2.11.1 底层原理
        • 2.2.11.1 案例
    • 3. 拓展
      • 3.1 iOS线程同步方案性能比较
      • 3.2 自旋锁、互斥锁比较
        • 3.2.1 什么情况使用自旋锁比较划算?
        • 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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/568901.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

uniapp APP检测更新

需求&#xff1a; 1.首次进入APP给出弹窗提示是否存在最新版本APP&#xff0c;可选择更新或者取消 2.选择取消后&#xff0c;在使用期间不再弹出该弹窗 3.在设置中增加按钮&#xff0c;点击进行版本检测&#xff0c;再弹窗 效果图&#xff1a; 使用到的插件&#xff1a;APP升…

“三三裂变”,实体书营销实操细节分享……

“三三裂变”实操细节 一、实验结果 “三三裂变”的实验,结果比较好。就是我们大概有300人报名,但实际行动的只有109人,大概有103人都完成了三个人的目标,也就是说我们通过109人裂变了475人,利润率是1:4.5左右,整个裂变的效率还是可以的,也就是说: 如果你用这种方法有…

3节点ubuntu24.04服务器docker-compose方式部署高可用elk+kafka日志系统并接入nginx日志

一&#xff1a;系统版本: 二&#xff1a;部署环境&#xff1a; 节点名称 IP 部署组件及版本 配置文件路径 机器CPU 机器内存 机器存储 Log-001 10.10.100.1 zookeeper:3.4.13 kafka:2.8.1 elasticsearch:7.7.0 logstash:7.7.0 kibana:7.7.0 zookeeper:/data/zookeep…

微服务项目实战-黑马头条(八):App端-文章ES搜索、MongoDB搜索记录和关键词联想

文章目录 一、今日内容介绍1.1 App端搜索-效果图1.2 今日内容 二、搭建ElasticSearch环境2.1 拉取镜像2.2 创建容器2.3 配置中文分词器 ik2.4 使用postman测试 三、app端文章搜索3.1 需求分析3.2 思路分析3.3 创建索引和映射3.4 数据初始化到索引库3.4.1 导入es-init到heima-le…

JS -正则表达式

正则表达式 关于正则表达式&#xff0c;其实我写过几篇了&#xff0c;但是真正的正则表达式其实主要用于定义一些字符串的规则&#xff0c;计算机根据给出的正则表达式&#xff0c;来检查一个字符串是否符合规则。 我们来看一下&#xff0c;在JS中如何创建正则表达式对象。 语…

HTML:PC和手机的自适应图形布局样例

作者:私语茶馆 1.前言 有时我们需要开发一个自适应PC和手机的HTML页面,由于屏幕大小不同,会涉及到自动部署。W3School提供了一个非常好的案例:Responsive Image Gallery。本文利用独立CSS文件详细介绍一下这个案例。 2.案例详细介绍 2.1.Project项目文件结构 企业级项目…

电脑问题快速判断

电脑开机没有任何反应 检查电源 检查电源是否有问题或损坏&#xff0c;可以短接方法检测 板电源卡口对自己接第四或第五根线&#xff0c;若风扇匀速转动&#xff0c;电源无问题&#xff0c;若不转动或转一下停一下&#xff0c;电源有问题 检查内部连线 确保主板上的线插的…

Docker常用命令(镜像、容器、网络)

一、镜像 1.1 存出镜像 将镜像保存成为本地文件 格式&#xff1a;docker save -o 存储文件名 存储的镜像docker save -o nginx nginx:latest 1.2 载入镜像 将镜像文件导入到镜像库中 格式&#xff1a;docker load < 存出的文件或docker load -i 存出的文件…

蓝桥杯2024年第十五届省赛真题-小球反弹

以下两个解法感觉都靠谱&#xff0c;并且网上的题解每个人答案都不一样&#xff0c;目前无法判断哪个是正确答案。 方法一&#xff1a;模拟 代码参考博客 #include <iostream> #include <cmath> #include <vector>using namespace std;int main() {const i…

Axure实现tab页面切换功能

1. 实现效果 2. 实现原理 创建两个标签&#xff0c;并实现点击时选中状态点击时&#xff0c;设置面板状态 3. 实现步骤 3.1 实现可切换的标签 在页面上拖拽两个矩形作为两个tab标签&#xff0c;并命名 tab1 和 tab2 设置每个矩形的边框显示&#xff0c;只显示下边框即可 …

Flutter 上架如何解决 ITMS-91053 问题

最近&#xff0c;我的 Flutter App 发布到 TestFlight 后&#xff0c;就会收到一封邮件&#xff1a;The uploaded build for YOUR APP has one or more issues. 上面的邮件主要是说&#xff0c;我的 App 缺少了调用 API 的声明&#xff0c;以前从来没看到过&#xff0c;上网一查…

WEB攻防-ASP中间件IIS文件上传解析安全漏洞

漏洞原理&#xff1a; 基于文件 IIS6.0默认不解析;号后面的内容&#xff0c;例如1.asp;.jpg会当成1.asp解析&#xff0c;相当于分号截断。 基于文件夹 IIS6.0会将/*.asp/文件夹下的文件当成asp解析。 案例&#xff1a; 写一个木马文件&#xff0c;并改为jpg后缀 GIF89agif8…

宁盾LDAP统一用户认证与单点登录:构建高效安全的企业身份认证

在信息化时代&#xff0c;企业面临着众多的应用系统和数据资源&#xff0c;如何有效地管理和保护这些资源&#xff0c;确保信息安全和高效利用&#xff0c;成为了企业信息化建设的核心问题。LDAP统一用户认证和单点登录&#xff08;SSO&#xff09;作为一种高效、安全的身份验证…

windows与linux搭建svn环境并自动更新代码

SVN搭建以及自动更新代码 目录 一、windows安装svn并且转成中文 第1步&#xff1a;先下载安装包如下下面是语言包与安装包 第2步&#xff1a;双击安装包一直点击下一步即可 第3步&#xff1a;双击安装中文语言包 第二步&#xff1a;勾选设置语言包 二、linux安装svn 第1步…

pycharm远程连接server

1.工具–部署–配置 2.部署完成后&#xff0c;将现有的项目的解释器设置为ssh 解释器。实现在远端开发 解释器可以使用/usr/bin/python3

构建安全高效的前端权限控制系统

✨✨谢谢大家捧场&#xff0c;祝屏幕前的小伙伴们每天都有好运相伴左右&#xff0c;一定要天天开心哦&#xff01;✨✨ &#x1f388;&#x1f388;作者主页&#xff1a; 喔的嘛呀&#x1f388;&#x1f388; ✨✨ 帅哥美女们&#xff0c;我们共同加油&#xff01;一起进步&am…

GDPU unity游戏开发 碰撞体与关节

让你设计的角色跑起来吧&#xff0c;可以是动画&#xff0c;也可以是碰撞器的运动。 运动小车 找到小车素材&#xff0c;导入到层级面板。然后可以新建一个地面让小车在上面运动&#xff0c;新建一个方块当障碍物。 摆放好后&#xff0c;要加组件。记住&#xff0c;在unity中运…

idea上传项目到gitee(码云)

1、打开码云&#xff0c;新建仓库 2、创建 3、这就是创建成功的页面 4、复制仓库地址&#xff0c;后面需要用到 2、打开我们的项目&#xff1a;例如我现在的项目 1、idea创建git仓库 2、选择我们项目文件夹的目录 3、查看文件是否变色&#xff0c;变色表示成功了 4、添加到缓…

unity cinemachine相机 (案例 跟随角色移动)

安装相机包 打开包管理工具 在 unity registry 搜索cinemachine 会在maincamera中生成一个组件cinemachineBrain 只能通过虚拟相机操控 主相机 虚拟相机的参数 案例 1.固定相机效果 位置 在固定的地方 默认的模式 2.相机跟随人物效果 焦距设置 20 跟随设置 把playere…

10.MMD 室内场景导入背景视频和灯光

导入背景视频 1. 导入人物和场景 场景是Akali’s room&#xff0c;可以在墙壁上添加视频 先添加主场景 2. 修改视频文件格式 在背景里选择导入背景视频文件 需要将mp4视频格式转化为AVI格式 方法一 先将视频导入格式工厂 点击配置 将视频编码改成DivX 再开始处理 …
最新文章