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.mzph.cn/pingmian/2428.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【Ne4j图数据库入门笔记1】图形数据建模初识

1.1 图形建模指南 图形数据建模是用户将任意域描述为节点的连接图以及与属性和标签关系的过程。Neo4j 图数据模型旨在以 Cypher 查询的形式回答问题&#xff0c;并通过组织图数据库的数据结构来解决业务和技术问题。 1.1.1 图形数据模型介绍 图形数据模型通常被称为对白板友…

【大模型】大模型时代的语音合成:音频的离散化表示

&#x1f512;文章目录 &#x1f4ca;什么是音频离散化&#x1f3ac;音频离散化是什么 &#x1f308;SoundStream、Encodec&#x1f302; SoundStream&#x1f680;Encodec &#x1f4ca;什么是音频离散化 &#x1f3ac;音频离散化是什么 在自然语言处理&#xff08;NLP&…

Amazon云计算AWS之[2]弹性计算云EC2

文章目录 说明EC2基本架构Amazon机器映象&#xff08;AMI&#xff09;实例&#xff08;Instance&#xff09;弹性块存储&#xff08;EBS&#xff09; EC2关键技术地理区域和可用区域EC2通信机制弹性负载均衡监控服务自动缩放服务管理控制台 EC2安全及容错机制EC2弹性IP地址 说明…

KingbaseES数据库copy导入导出

数据库版本&#xff1a;KingbaseES V008R006C008B0014 文章目录如下 1. 语法说明 2. 导出数据 2.1. 基本用法 2.2. 只导出某列 2.3. 指定分隔符 2.4. 导出为二进制格式 2.5. 替换导出的NULL数据 2.6. 导出SELECT查询结果 3. 导入数据 3.1. 基本用法 3.2. 只导入某列…

恒峰智慧科技—森林消防泵:既可灭除火灾,又可清理水患

在广袤的森林中&#xff0c;火灾与水患如同潜伏的猛兽&#xff0c;时刻威胁着生态的安全。然而&#xff0c;随着科技的进步&#xff0c;我们有了更强大的武器来对抗这些威胁——森林消防泵。这款神奇的设备不仅能迅速扑灭火灾&#xff0c;还能在雨季到来时清理水患&#xff0c;…

Linux:进程与计划任务

文章目录 Linux&#xff1a;进程与计划任务一、进程1、进程是什么2、进程状态 二、列出进程命令1、查看静态的进程统计信息——“ps”Play1&#xff1a;“ps aux”Play2:ps -elf 2、查看静态的进程统计信息——“top”段首解析进程信息区解释 三、运行与终止进程3.1、运行进程3…

spring的跨域问题

跨域问题 什么是跨域解决跨域 什么是跨域 跨域问题本质是浏览器的一种保护机制&#xff0c;它的初衷是为了保证用户的安全&#xff0c;防止恶意网站窃取数据。如果出现了以下情况中的任意一种&#xff0c;那么它就是跨域请求&#xff1a; 1、协议不同&#xff0c;如 http 和 h…

Redis入门到通关之Redis数据结构-List篇

文章目录 ☃️概述☃️数据结构☃️源码☃️其他 欢迎来到 请回答1024 的博客 &#x1f353;&#x1f353;&#x1f353;欢迎来到 请回答1024的博客 关于博主&#xff1a; 我是 请回答1024&#xff0c;一个追求数学与计算的边界、时间与空间的平衡&#xff0c;0与1的延伸的后端…

8、案例实战【处理百万级交易无压力】:支付系统JVM调优实战指南

8.1、前文回顾 本文将以一个日交易量达百万次的支付系统为背景,为大家深入分析在上线部署一个系统时,如何根据系统的业务量来合理设置JVM的堆内存大小。 通过阅读之前的文章,相信大家已经对编写的代码如何在JVM中运行的基本原理有了一定的了解,同时也知道如何通过参数来设…

数据结构(Wrong Question)

一、绪论 1.1 数据结构的基本概念 D 因为抽象数据类型&#xff08;ADT&#xff09;描述了数据的逻辑结构和抽象运算&#xff0c;通常用&#xff08;数据对象&#xff0c;数据对象&#xff0c;基本操作集&#xff09;这样的三元组来表示&#xff0c;从而可构成一个完整的数据结…

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…

(Oracle)SQL优化案例:组合索引优化

项目场景 项目上的ETL模型里有如下SQL语句。执行速度非常慢&#xff0c;每次只查询200条数据&#xff0c;但却需要20多秒的时间。再加上该SQL查询出的数据同步频率很高&#xff0c;这个速度是完全不能忍受的。 因为项目隐私&#xff0c;所以对表及字段做了改写。 SELECT ID…

DaVinci Fusion Studio 19 for Mac/win:影视后期特效合成的巅峰之作

在影视后期制作的广袤天地里&#xff0c;一款强大的特效合成软件如同一位技艺高超的魔法师&#xff0c;能够化腐朽为神奇&#xff0c;将普通的影像素材转变为震撼人心的视觉盛宴。而DaVinci Fusion Studio 19&#xff0c;正是这样一款备受影视从业者推崇的巅峰之作。 无论是Ma…

基于Python+Selenium+Pytest的Dockerfile如何写

使用 Dockerfile 部署 Python 应用程序与 Selenium 测试 在本文中&#xff0c;我们将介绍如何使用 Dockerfile 部署一个 Python 应用程序&#xff0c;同时利用 Selenium 进行自动化测试。我们将使用官方的 Python 运行时作为父镜像&#xff0c;并在其中安装所需的依赖项和工具…

Android视角看鸿蒙第十二课-鸿蒙的布局之相对布局RelativeContainer

Android视角看鸿蒙第十二课-鸿蒙的布局之相对布局RelativeContainer 导读 相对布局和线性、层叠布局一样都是类似于Android布局的&#xff0c;之前两篇文章已经了解线性、层叠布局的使用方法&#xff0c;这篇文章一起来学习下鸿蒙中的相对布局。 之前的文章中&#xff0c;我偶…

VNISEdit 制作安装包

1. 环境依赖 1.1. NSIS 下载 下载地址&#xff1a;https://nsis.sourceforge.io/Download 1.2. VNISEdit 下载 下载地址1&#xff1a;https://sourceforge.net/projects/hmne/ 下载 exe 安装。 下载地址2&#xff1a;https://hmne.sourceforge.net/ 可以下载 exe 安装。也…

centos 安装配置文件中心 nacos2.2.3 稳定版

安装mysql 8 参考文章 centos7搭建mysql5.6 && mysql 8.0_centos7 mysql5.6-CSDN博客 安装 jdk 17 官网下载 对应的版本 Java Downloads | Oracle wget https://download.java.net/java/GA/jdk17.0.2/dfd4a8d0985749f896bed50d7138ee7f/8/GPL/openjdk-17.0.2_l…

在excel中,如何在一个表中删除和另一个表中相同的数据?

现在有A表&#xff0c;是活动全部人员的姓名和学号&#xff0c;B表是该活动中获得优秀人员的姓名和学号&#xff0c; 怎么提取没有获得优秀人员的名单&#xff1f; 这里提供两个使用excel基础功能的操作方法。 1.条件格式自动筛选 1.1按住Ctrl键&#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;上网一查…

SpringBoot+Vue开发记录(三)

说明&#xff1a;本篇文章的主要内容为需求分析。需求分析这一部分很重要&#xff0c;也稍微有点子难搞&#xff0c;所以本篇文章里的有些内容会有失偏颇。 一、准备步骤 我打算做一个刷题项目&#xff0c;但是具体这个项目该怎么做&#xff0c;我是一头雾水。 所以就要先进行…