文章目录
- 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块资源可能会被多个线程共享,也就是
-
- 比如多个线程访问同一个对象、同一个变量、同一个文件
-
当多个线程访问同一块资源时,很容易引发
数据错乱
和数据安全
问题
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
方法加锁后,调用otherTest2
,otherTest2
方法开始执行时也会加锁
,此时因为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);
此时可以正常执行完otherTest
、otherTest2
方法
如果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),用来指示某任务或者队列的运行优先级
;
-
记录了持有者的
api
都可以自动避免
优先级反转,系统会通过提高
相关线程的优先级来解决优先级反转的问题,如dispatch_sync
, 如果系统不知道持有者所在的线程,则无法知道应该提高谁的优先级,也就无法解决反转问题。 -
慎用
dispatch_semaphore
做线程同步
dispatch_semaphore
容易造成优先级反转,因为api没有
记录是哪个线程持有了信号量,所以有高优先级
的线程在等待锁的时候,内核无法知道该提高那个线程的优先级(QoS);
dispatch_semaphore
不能避免优先级反转的原因
在调用dispatch_semaphore_wait()
的时候,系统不知道哪个线程会调用 dispatch_semaphore_signal()
方法,系统无法知道owner
信息,无法调整
优先级。dispatch_group
和semaphore
类似,在调用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