一.多线程的安全隐患以及解决方案
1.隐患
资源共享
1块资源可能会被多个线程共享,也就是多个线程可能会访问同一块资源
比如多个线程访问同一个对象、同一个变量、同一个文件当多个线程访问同一块资源时,很容易引发数据错乱和数据安全问题
2.隐患解决方案:
- 解决方案:使用
线程同步
技术(同步,就是协同步调,按预定的先后次序进行) - 常见的线程同步技术是:
加锁
不是多线程就需要锁;多个线程访问一个值的话不需要;如果多个线程里面,即修改值、又访问这个值,就会有不一致的情况,所以需要锁
二.锁
1.自旋锁 OSSpinLock
OSSpinLock叫做”自旋锁”,
等待锁的线程会处于忙等(busy-wait)状态,一直占用着CPU资源
目前已经不再安全,可能会出现优先级反转问题
比如有两个线程,一个线程优先级高、一个优先级低。线程开始的时候,优先级低的线程先进入了线程,锁住了。
优先级高的线程就会处于等待状态、一直占用着CPU。因为线程优先级高,优先级低的线程无法进行,就无法解锁。造成死锁。
(即:如果等待锁的线程优先级较高,它会一直占用着CPU资源,优先级低的线程就无法释放锁)需要导入头文件#import
#import
//锁是结构体,所以用assign修饰
@property (assign, nonatomic) OSSpinLock moneyLock;
@implementation OSSpinLockDemo
- (instancetype)init
{
if (self = [super init]) {
self.moneyLock = OS_SPINLOCK_INIT;
}
return self;
}
- (void)__drawMoney
{
OSSpinLockLock(&_moneyLock);
[super __drawMoney];
OSSpinLockUnlock(&_moneyLock);
}
- (void)__saveMoney
{
OSSpinLockLock(&_moneyLock);
[super __saveMoney];
OSSpinLockUnlock(&_moneyLock);
}
@end
2. 互斥锁
2.1 os_unfair_lock(替代OSSpinLock)
- os_unfair_lock用于
取代不安全的OSSpinLock ,从iOS10开始才支持
- 从底层调用看,等待os_unfair_lock锁的线程会处于
休眠状态
,并非忙等 -
需要导入头文件#import
#import
@property (assign, nonatomic) os_unfair_lock moneyLock;
@implementation OSUnfairLockDemo
- (instancetype)init
{
if (self = [super init]) {
self.moneyLock = OS_UNFAIR_LOCK_INIT;
}
return self;
}
- (void)__saveMoney
{
os_unfair_lock_lock(&_moneyLock);
[super __saveMoney];
os_unfair_lock_unlock(&_moneyLock);
}
- (void)__drawMoney
{
os_unfair_lock_lock(&_moneyLock);
[super __drawMoney];
os_unfair_lock_unlock(&_moneyLock);
}
@end
2.2 pthread_mutex
- mutex叫做”互斥锁”,
等待锁的线程会处于休眠状态
-
需要导入头文件#import
(pthread开头的是跨平台的,linux、oc都可)
#import
@property (assign, nonatomic) pthread_mutex_t moneyMutex;
- (void)__initMutex:(pthread_mutex_t *)mutex
{
// 初始化属性
pthread_mutexattr_t attr;
pthread_mutexattr_init(&attr);
pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_DEFAULT);
// 初始化锁
pthread_mutex_init(mutex, attr);
// 销毁属性
pthread_mutexattr_destroy(&attr);
}
- (instancetype)init
{
if (self = [super init]) {
[self __initMutex:&_moneyMutex];
}
return self;
}
- (void)__saveMoney
{
pthread_mutex_lock(&_moneyMutex);
[super __saveMoney];
pthread_mutex_unlock(&_moneyMutex);
}
- (void)__drawMoney
{
pthread_mutex_lock(&_moneyMutex);
[super __drawMoney];
pthread_mutex_unlock(&_moneyMutex);
}
- (void)dealloc
{
pthread_mutex_destroy(&_moneyMutex);
}
@end
3. 递归锁
3.1 pthread_mutex – 递归锁
- 把pthread_mutex属性的type设置为递归(OTHREAD_MUTEX_RECURSIVE)即可。
- 允许同一个线程对一把锁进行重复加锁
#import
@property (assign, nonatomic) pthread_mutex_t mutex;
@implementation MutexDemo2
- (void)__initMutex:(pthread_mutex_t *)mutex
{
// 递归锁:允许同一个线程对一把锁进行重复加锁
// 初始化属性
pthread_mutexattr_t attr;
pthread_mutexattr_init(&attr);
pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE);
// 初始化锁
pthread_mutex_init(mutex, &attr);
// 销毁属性
pthread_mutexattr_destroy(&attr);
}
- (instancetype)init
{
if (self = [super init]) {
[self __initMutex:&_mutex];
}
return self;
}
/**
线程1:otherTest(+-)
otherTest(+-)
otherTest(+-)
线程2:otherTest(等待)
*/
- (void)otherTest
{
pthread_mutex_lock(&_mutex);
NSLog(@"%s", __func__);
static int count = 0;
if (count < 10) {
count++;
[self otherTest];
}
pthread_mutex_unlock(&_mutex);
}
- (void)dealloc
{
pthread_mutex_destroy(&_mutex);
}
@end
4. 条件锁
4.1 pthread_mutex – 条件
// 等待(解锁--》添加重新激活的条件--》等待)
pthread_cond_wait(&_cond, &_mutex);
// 信号(发出信号,告诉其他线程这个条件以满足,可以继续执行了。)
pthread_cond_signal(&_cond);
// 广播(如果有多个线程在等待这个条件,用这个广播发出)
// pthread_cond_broadcast(&_cond);
// 解锁(其他线程受到满足条件的通知后,也需要这里解锁后才能进行加锁)
pthread_mutex_unlock(&_mutex);
@interface MutexDemo3()
@property (assign, nonatomic) pthread_mutex_t mutex;
@property (assign, nonatomic) pthread_cond_t cond;
@property (strong, nonatomic) NSMutableArray *data;
@end
@implementation MutexDemo3
- (instancetype)init
{
if (self = [super init]) {
// 初始化属性
pthread_mutexattr_t attr;
pthread_mutexattr_init(&attr);
pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE);
// 初始化锁
pthread_mutex_init(&_mutex, &attr);
// 销毁属性
pthread_mutexattr_destroy(&attr);
// 初始化条件
pthread_cond_init(&_cond, NULL);
self.data = [NSMutableArray array];
}
return self;
}
- (void)otherTest
{
[[[NSThread alloc] initWithTarget:self selector:@selector(__remove) object:nil] start];
[[[NSThread alloc] initWithTarget:self selector:@selector(__add) object:nil] start];
}
// 生产者-消费者模式
// 线程1
// 删除数组中的元素
- (void)__remove
{
pthread_mutex_lock(&_mutex);
NSLog(@"__remove - begin");
if (self.data.count == 0) {
// 等待(解锁--》添加重新激活的条件--》等待)
pthread_cond_wait(&_cond, &_mutex);
}
[self.data removeLastObject];
NSLog(@"删除了元素");
pthread_mutex_unlock(&_mutex);
}
// 线程2
// 往数组中添加元素
- (void)__add
{
pthread_mutex_lock(&_mutex);
sleep(1);
[self.data addObject:@"Test"];
NSLog(@"添加了元素");
// 信号(发出信号,告诉其他线程这个条件以满足,可以继续执行了。)
pthread_cond_signal(&_cond);
// 广播(如果有多个线程在等待这个条件,用这个广播发出)
// pthread_cond_broadcast(&_cond);
// 解锁(其他线程受到满足条件的通知后,也需要这里解锁后才能进行加锁)
pthread_mutex_unlock(&_mutex);
}
- (void)dealloc
{
pthread_mutex_destroy(&_mutex);
pthread_cond_destroy(&_cond);
}
@end
三.锁的封装
1. NSLock(NSLock是对mutex普通锁的封装)
- (instancetype)init
{
if (self = [super init]) {
self.ticketLock = [[NSLock alloc] init];
}
return self;
}
- (void)__saveMoney
{
[self.moneyLock lock];
[super __saveMoney];
[self.moneyLock unlock];
}
- (void)__drawMoney
{
[self.moneyLock lock];
[super __drawMoney];
[self.moneyLock unlock];
}
@end
2. NSRecursiveLock(也是对mutex递归锁的封装,API跟NSLock基本一致)
3. NSCondition(NSCondition是对mutex和cond的封装)
@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];
sleep(2);
[self.condition unlock];
}
@end
4. NSConditionLock(对NSCondition的封装、可添加依赖条件)
@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 lock];
NSLog(@"__one");
sleep(1);
[self.conditionLock unlockWithCondition:2];
}
- (void)__two
{
[self.conditionLock lockWhenCondition:2];
NSLog(@"__two");
sleep(1);
[self.conditionLock unlockWithCondition:3];
}
- (void)__three
{
[self.conditionLock lockWhenCondition:3];
NSLog(@"__three");
[self.conditionLock unlock];
}
@end
四. 其他同步方案
1. 信号量 dispatch_semaphore
- semaphore叫做”信号量”
- 信号量的初始值,可以用来控制线程并发访问的最大数量
-
信号量的初始值为1,代表同时只允许1条线程访问资源,保证线程同步
@interface SemaphoreDemo()
@property (strong, nonatomic) 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];
}
}
// 线程10、7、6、9、8
- (void)test
{
// 如果信号量的值 > 0,就让信号量的值减1,然后继续往下执行代码
// 如果信号量的值 <= 0,就会休眠等待,直到信号量的值变成>0,就让信号量的值减1,然后继续往下执行代码
dispatch_semaphore_wait(self.semaphore, DISPATCH_TIME_FOREVER);
sleep(2);
NSLog(@"test - %@", [NSThread currentThread]);
// 让信号量的值+1
dispatch_semaphore_signal(self.semaphore);
}
@end
2. 直接使用GCD的串行队列,也是可以实现线程同步的
3. synchroized (对mutex递归锁封装)
- @synchronized是对mutex递归锁的封装
- 源码查看:objc4中的objc-sync.mm文件
- @synchronized(obj)内部会生成obj对应的递归锁,然后进行加锁、解锁操作
- (void)__saveMoney
{
@synchronized([self class]) { // objc_sync_enter
[super __saveMoney];
} // objc_sync_exit
}
- (void)__saleTicket
{
static NSObject *lock;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
lock = [[NSObject alloc] init];
});
@synchronized(lock) {
[super __saleTicket];
}
}
五. 锁方案的性能比较
(推荐用:信号量、phtread_mutex)
- 性能从高到低排序
os_unfair_lock(ios 10以后才可用)
OSSpinLock(会造成反转)
dispatch_semaphore(信号量)
pthread_mutex(互斥、递归、条件都可以)
dispatch_queue(DISPATCH_QUEUE_SERIAL)
NSLock
NSCondition
pthread_mutex(recursive)
NSRecursiveLock
NSConditionLock
@synchronized
自旋锁、互斥锁比较:
什么情况使用自旋锁比较划算?
预计线程等待锁的时间很短
加锁的代码(临界区)经常被调用,但竞争情况很少发生
CPU资源不紧张
多核处理器什么情况使用互斥锁比较划算?
预计线程等待锁的时间较长
单核处理器
临界区有IO操作(文件存储操作)
临界区代码复杂或者循环量大
临界区竞争非常激烈
六.automic:
给属性添加上atomic修饰,可以保证书新的setter和getter都是原子性操作,也就是保证getter和setter内部都是线程同步的
它并不能保证使用属性的过程是线程安全的
automic是在set方法、get方法加锁。对于数组,往里面添加数据其实是不安全的。
七.iOS中的读写安全方案:
多读单写:
同一时间,只能有1个线程进行写的操作
同一时间,允许有多个线程进行读的操作
同一时间,不允许既有写的操作,又有读的操作
多读单写实现方案
pthread_rwlock:读写锁;
dispatch_barrier_async:异步栅栏调用;
1. pthread_rwlock
等待锁的线程会进入休眠
#import
@interface ViewController ()
@property (assign, nonatomic) pthread_rwlock_t lock;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
// 初始化锁
pthread_rwlock_init(&_lock, NULL);
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
for (int i = 0; i < 10; i++) {
dispatch_async(queue, ^{
[self read];
});
dispatch_async(queue, ^{
[self write];
});
}
}
- (void)read {
pthread_rwlock_rdlock(&_lock);
sleep(1);
NSLog(@"%s", __func__);
pthread_rwlock_unlock(&_lock);
}
- (void)write
{
pthread_rwlock_wrlock(&_lock);
sleep(1);
NSLog(@"%s", __func__);
pthread_rwlock_unlock(&_lock);
}
- (void)dealloc
{
pthread_rwlock_destroy(&_lock);
}
@end
2. dispatch_barrier_async
- 这个函数传入的并发队列必须是自己通过dispatch_queue_cretate创建的
- 如果传入的是一个串行或是一个全局的并发队列,那这个函数便等同于dispatch_async函数的效果