问题
多个线程访问同一资源时会引发数据混乱。
示例:火车站卖票
假设有100张票,同时开10个窗口买票
//卖票演示
-(void)sellTicket{
self.totalTickets = 100;
for (NSInteger i = 0; i < 10; i++){
dispatch_async(dispatch_get_global_queue(0, 0), ^{
for (int i = 0; i < 10; i++) {
[self sellTicketMethord];
}
});
}
}
-(void)sellTicketMethord{
int oldTickets = self.totalTickets;
sleep(.5);
oldTickets -= 1;
self.totalTickets = oldTickets;
NSLog(@"当前剩余票数>>>>> %d", oldTickets);
}
结果:
最后剩余票数仍大于0;
分析:
多条线程同时访问同一资源(剩余票数)时,读取到的剩余票数相同,卖出去多张票,但实际上总数只减少1张。
解决方案:
线程加锁。
常见锁类型
自旋锁:会不断的进行尝试请求.如:OSSpinLock.
互斥锁:对于某一资源同时只允许有一个访问,无论读写,会进入休眠.如:NSLock.
读写锁:对于某一资源同时只允许有一个写访问或多个读访问或读写只能一个.如:pthread_rwlock,dispatch_barrier_async.
条件锁:在满足某个条件的时候进行加锁或者解锁.如:NSConditionLock.
递归锁:可以被一个线程多次获得,而不会引起死锁.它记录了成功获得锁的次数,每一次成功的获得锁,必须有一个配套的释放锁和其对应,这样才不会引起死锁.只有当所有的锁被释放之后,其他线程才可以获得锁.如:NSRecursiveLock
常用锁
OSSpinLock
OSSpinLock 是一种自旋锁。使用时要导入头文件 #import <libkern/OSAtomic.h>
常用方法:
//初始化
OSSpinLock lock = OS_SPINLOCK_INIT;
//加锁,参数为OSSPINLOCK地址
OSSpinLockLock(&lock)
//解锁,参数是OSSpinLock地址
OSSpinLockUnlock(&lock)
//尝试上锁,参数是OSSpinLock地址。如果返回false,表示上锁失败,锁正在被其他线程持有。如果返回true,表示上锁成功
OSSpinLockTry(&lock)
使用示例:
-(void)sellTicket{
self.ticketLock = OS_SPINLOCK_INIT;
self.totalTickets = 100;
for (NSInteger i = 0; i < 10; i++){
dispatch_async(dispatch_get_global_queue(0, 0), ^{
for (int i = 0; i < 10; i++) {
[self sellTicketMethord];
}
});
}
}
-(void)sellTicketMethord{
OSSpinLockLock(&_ticketLock);
int oldTickets = self.totalTickets;
sleep(.5);
oldTickets -= 1;
self.totalTickets = oldTickets;
NSLog(@"当前剩余票数>>>>> %d", oldTickets);
OSSpinLockUnlock(&_ticketLock);
}
注意⚠️:OSSpinLock 在iOS10.0以后就被弃用了,可以使用 os_unfair_lock_lock 替代。而且还有一些安全性问题。
OSSpinLock 存在的问题?
iOS 系统维护了 5 个不同的线程优先级/QoS: background,utility,default,user-initiated,user-interactive。高优先级线程始终会在低优先级线程前执行,一个线程不会受到比它更低优先级线程的干扰。这种线程调度算法会产生潜在的优先级反转问题,从而破坏了 spin lock。
具体来说,如果一个低优先级的线程获得锁并访问共享资源,这时一个高优先级的线程也尝试获得这个锁,它会处于 spin lock 的忙等状态从而占用大量 CPU。此时低优先级线程无法与高优先级线程争夺 CPU 时间,从而导致任务迟迟完不成、无法释放 lock。
对于这个问题,一种解决方案是用 truly unbounded backoff 算法,这能避免 livelock 问题,但如果系统负载高时,它仍有可能将高优先级的线程阻塞数十秒之久;另一种方案是使用 handoff lock 算法,这也是 libobjc 目前正在使用的。锁的持有者会把线程 ID 保存到锁内部,锁的等待者会临时贡献出它的优先级来避免优先级反转的问题。理论上这种模式会在比较复杂的多锁条件下产生问题,但实践上目前还一切都好。
libobjc 里用的是 Mach 内核的 thread_switch() 然后传递了一个 mach thread port 来避免优先级反转,另外它还用了一个私有的参数选项,所以开发者无法自己实现这个锁。另一方面,由于二进制兼容问题,OSSpinLock 也不能有改动。
最终的结论就是,除非开发者能保证访问锁的线程全部都处于同一优先级,否则 iOS 系统中所有类型的自旋锁都不能再使用了。
os_unfair_lock
os_unfair_lock 用于取代不安全的 OSSpinLock,从 iOS10 开始才支持。从底层调用看,等待 os_unfair_lock 锁的线程会处于休眠状态,并非忙等。需要导入头文件#import <os/lock.h>
常用方法
//初始化
os_unfair_lock lock = OS_UNFAIR_LOCK_INIT;
//加锁
os_unfair_lock_lock(&lock);
//解锁
os_unfair_lock_unlock(&lock);
示例:
1、导入头文件
#import <os/lock.h>
2、设置全局属性
@property (assign, nonatomic) os_unfair_lock ticketLock;
3、使用:
-(void)sellTicket{
self.ticketLock = OS_UNFAIR_LOCK_INIT;
self.totalTickets = 100;
for (NSInteger i = 0; i < 10; i++){
dispatch_async(dispatch_get_global_queue(0, 0), ^{
for (int i = 0; i < 10; i++) {
[self sellTicketMethord];
}
});
}
}
-(void)sellTicketMethord{
os_unfair_lock_lock(&_ticketLock);
int oldTickets = self.totalTickets;
sleep(.5);
oldTickets -= 1;
self.totalTickets = oldTickets;
NSLog(@"当前剩余票数>>>>> %d", oldTickets);
os_unfair_lock_unlock(&_ticketLock);
}
pthread_mutex
mutex 是一种互斥锁。需要导入头文件#import <pthread.h> 。
常用方法:
1、初始化锁的属性
pthread_mutexattr_t attr;
pthread_mutexattr_init(&attr);
pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE);
/*
* Mutex type attributes
*/
#define PTHREAD_MUTEX_NORMAL 0
#define PTHREAD_MUTEX_ERRORCHECK 1
#define PTHREAD_MUTEX_RECURSIVE 2
#define PTHREAD_MUTEX_DEFAULT PTHREAD_MUTEX_NORMAL
2、初始化锁
pthread_mutex_init(mutex, &attr);
3、初始化锁结束以后,销毁属性
pthread_mutexattr_destroy(&attr);
4、加锁解锁
pthread_mutex_lock(&_mutex);
pthread_mutex_unlock(&_mutex);
5、销毁锁
pthread_mutex_destroy(&_mutex);
1、导入头文件
#import <pthread.h>
2、设置全局属性
@property (assign, nonatomic) pthread_mutex_t ticketMutex;
3、示例
-(void)sellTicket{
//初始化属性
pthread_mutexattr_t attr;
pthread_mutexattr_init(&attr);
pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_DEFAULT);
// 初始化锁
pthread_mutex_init(&(_ticketMutex), &attr);
// 销毁属性
pthread_mutexattr_destroy(&attr);
self.totalTickets = 100;
for (NSInteger i = 0; i < 10; i++){
dispatch_async(dispatch_get_global_queue(0, 0), ^{
for (int i = 0; i < 10; i++) {
[self sellTicketMethord];
}
});
}
}
-(void)sellTicketMethord{
pthread_mutex_lock(&_ticketMutex);
int oldTickets = self.totalTickets;
sleep(.5);
oldTickets -= 1;
self.totalTickets = oldTickets;
NSLog(@"当前剩余票数>>>>> %d", oldTickets);
pthread_mutex_unlock(&_ticketMutex);
}
递归锁
稍微修改一下以上代码(修改 sellTicketMethord 方法中代码)
-(void)sellTicketMethord{
pthread_mutex_lock(&_ticketMutex);
int oldTickets = self.totalTickets;
sleep(.5);
oldTickets -= 1;
self.totalTickets = oldTickets;
NSLog(@"当前剩余票数>>>>> %d", oldTickets);
//⚠️ 此处添加
[self sellingTickets2];
pthread_mutex_unlock(&_ticketMutex);
}
- (void)sellingTickets2{
pthread_mutex_lock(&_ticketMutex);
NSLog(@"%s",__func__);
pthread_mutex_unlock(&_ticketMutex);
}
上面的代码就会造成线程死锁,因为方法 sellTicketMethord 的结束需要 sellingTickets2 解锁,方法 sellingTickets2 的结束需要 sellTicketMethord 解锁,相互引用造成死锁.但是 pthread_mutex_t 里面有一个属性可以解决这个问题 PTHREAD_MUTEX_RECURSIVE.
PTHREAD_MUTEX_RECURSIVE 递归锁:允许同一个线程对同一把锁进行重复加锁.
更改后代码
-(void)sellTicket{
//初始化属性
pthread_mutexattr_t attr;
pthread_mutexattr_init(&attr);
pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE);//⚠️更改在此
// 初始化锁
pthread_mutex_init(&(_ticketMutex), &attr);
// 销毁属性
pthread_mutexattr_destroy(&attr);
self.totalTickets = 100;
for (NSInteger i = 0; i < 10; i++){
dispatch_async(dispatch_get_global_queue(0, 0), ^{
for (int i = 0; i < 10; i++) {
[self sellTicketMethord];
}
});
}
}
-(void)sellTicketMethord{
pthread_mutex_lock(&_ticketMutex);
int oldTickets = self.totalTickets;
sleep(.5);
oldTickets -= 1;
self.totalTickets = oldTickets;
NSLog(@"当前剩余票数>>>>> %d", oldTickets);
//⚠️ 此处添加
[self sellingTickets2];
pthread_mutex_unlock(&_ticketMutex);
}
- (void)sellingTickets2{
pthread_mutex_lock(&_ticketMutex);
NSLog(@"%s",__func__);
pthread_mutex_unlock(&_ticketMutex);
}
注:对于上面的问题还有一个解决方案就是在方法 sellingTickets2 中重新在创建一把新的锁,两个方法的锁对象不同,就不会造成线程死锁了。
条件锁
满足一定条件后再进行加锁或者解锁。
// 初始化属性
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_t condition
pthread_cond_init(&_cond, NULL);
// 等待条件
pthread_cond_wait(&_cond, &_mutex);
//激活一个等待该条件的线程
pthread_cond_signal(&_cond);
//激活所有等待该条件的线程
pthread_cond_broadcast(&_cond);
//销毁资源
pthread_mutex_destroy(&_mutex);
pthread_cond_destroy(&_cond);
使用案例:假设我们有一个数组,里面有两个线程,一个是添加数组,一个是删除数组,我们先调用删除数组,在调用添加数组,但是在数组为空的时候不调用删除数组。
1、添加头文件
#import <pthread.h>
2、添加全局属性
@property (assign, nonatomic) pthread_mutex_t mutex;
@property (assign, nonatomic) pthread_cond_t cond;
@property (strong, nonatomic) NSMutableArray *data;
3、初始化属性
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];
4、使用
// 线程1
// 删除数组中的元素
- (void)remove{
pthread_mutex_lock(&_mutex);
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_mutex_unlock(&_mutex);
}
- (void)dealloc{
pthread_mutex_destroy(&_mutex);
pthread_cond_destroy(&_cond);
}
NSLock
NSLock 是有一种互斥锁。是对 mutex 普通锁的封装。pthread_mutex_init(mutex, NULL);
NSLock 遵循 NSLocking 协议。
//加锁
Lock
//解锁
unlock
//尝试加锁
tryLock :如果失败的话返回 NO,
//指定 Date 之前尝试加锁
lockBeforeDate: 如果在指定时间之前都不能加锁,则返回NO
-(void)sellTicket{
self.ticketLock = [[NSLock alloc] init];
self.totalTickets = 100;
for (NSInteger i = 0; i < 10; i++){
dispatch_async(dispatch_get_global_queue(0, 0), ^{
for (int i = 0; i < 10; i++) {
[self sellTicketMethord];
}
});
}
}
-(void)sellTicketMethord{
[self.ticketLock lock];
int oldTickets = self.totalTickets;
sleep(.5);
oldTickets -= 1;
self.totalTickets = oldTickets;
NSLog(@"当前剩余票数>>>>> %d", oldTickets);
[self.ticketLock unlock];
}
NSRecursiveLock
NSRecursiveLock 是一种递归锁。是对 mutex 递归锁的封装。API 跟 NSLock 基本一致。
-(void)sellTicket{
self.ticketLock = [[NSRecursiveLock alloc] init];
self.totalTickets = 100;
for (NSInteger i = 0; i < 10; i++){
dispatch_async(dispatch_get_global_queue(0, 0), ^{
for (int i = 0; i < 10; i++) {
[self sellTicketMethord];
}
});
}
}
-(void)sellTicketMethord{
[self.ticketLock lock];
int oldTickets = self.totalTickets;
sleep(.5);
oldTickets -= 1;
self.totalTickets = oldTickets;
NSLog(@"当前剩余票数>>>>> %d", oldTickets);
[self.ticketLock unlock];
}
NSCondition
NSCondition 是一种条件锁。是对 mutex 和 cond 的封装。
@interface NSCondition : NSObject <NSLocking> {
- (void)wait;
- (BOOL)waitUntilDate:(NSDate *)limit;
- (void)signal;
- (void)broadcast;
@property (nullable, copy) NSString *name
@end
- (void)conditionTest {
for (int i = 0; i < 50; i++) {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
[self addTickets];
});
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
[self addTickets];
});
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
[self minusTickets];
});
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
[self minusTickets];
});
}
}
- (void)addTickets {
[self.condition lock];
self.ticketCount = self.ticketCount + 1;
NSLog(@"现有ticketCount==%zd",self.ticketCount);
[self.condition unlock];
[self.condition signal];
}
- (void)minusTickets {
[self.condition lock];
while (self.ticketCount == 0) {
NSLog(@"==没有ticketCount==");
[self.condition wait];
return;
}
self.ticketCount -= 1;
NSLog(@"减一个,剩下ticketCount==%zd",self.ticketCount);
[self.condition unlock];
}
NSConditionLock
NSConditionLock 是一种递归锁,是对 NSCondition 的封装,自带条件探测,能够更简单灵活的使用。
@interface NSConditionLock : NSObject <NSLocking> {
- (instancetype)initWithCondition:(NSInteger)condition;//初始化Condition,并且设置状态值
@property (readonly) NSInteger condition;
- (void)lockWhenCondition:(NSInteger)condition;//当状态值为condition的时候加锁
- (BOOL)tryLock;
- (BOOL)tryLockWhenCondition:(NSInteger)condition;
- (void)unlockWithCondition:(NSInteger)condition;//当状态值为condition的时候解锁
- (BOOL)lockBeforeDate:(NSDate *)limit;
- (BOOL)lockWhenCondition:(NSInteger)condition beforeDate:(NSDate *)limit;
@property (nullable, copy) NSString *name;
@end
示例
- (void)conditionLockTest {
NSConditionLock *conditionLock = [[NSConditionLock alloc] initWithCondition:2];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
[conditionLock lockWhenCondition:1];
NSLog(@"线程1");
[conditionLock unlockWithCondition:0];
});
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0), ^{
[conditionLock lockWhenCondition:2];
NSLog(@"线程2");
[conditionLock unlockWithCondition:1];
});
dispatch_async(dispatch_get_global_queue(0, 0), ^{
[conditionLock lock];
NSLog(@"线程3");
[conditionLock unlock];
});
}
dispatch_semaphore
dispatch_semaphore 信号量。信号量的初始值,可以用来控制线程并发访问的最大数量。信号量的初始值为1,代表同时只允许1条线程访问资源,可以用来保证线程同步。
常用方法:
//表示最多开启5个线程
dispatch_semaphore_create(5);
// 如果信号量的值 > 0,就让信号量的值减1,然后继续往下执行代码
// 如果信号量的值 <= 0,就会休眠等待,直到信号量的值变成>0,就让信号量的值减1,然后继续往下执行代码
dispatch_semaphore_wait(self.semaphore, DISPATCH_TIME_FOREVER);
// 让信号量的值+1
dispatch_semaphore_signal(self.semaphore);
示例
@interface dispatch_semaphoreDemo()
@property (strong, nonatomic) dispatch_semaphore_t semaphore;
@end
@implementation dispatch_semaphoreDemo
- (instancetype)init{
if (self = [super init]) {
self.semaphore = 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{
// 如果信号量的值 > 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
dispatch_queue
dispatch_queue 使用串行队列实现线程同步。
dispatch_queue_t queue = dispatch_queue_create("test", DISPATCH_QUEUE_SERIAL);
dispatch_sync(queue, ^{
// 追加任务1
for (int i = 0; i < 2; ++i) {
NSLog(@"1---%@",[NSThread currentThread]);
}
});
dispatch_sync(queue, ^{
// 追加任务2
for (int i = 0; i < 2; ++i) {
NSLog(@"2---%@",[NSThread currentThread]);
}
});
@synchronized
@synchronized 是一种递归锁,是对 mutex 递归锁的封装, @synchronized(obj) 内部会生成obj 对应的递归锁,然后进行加锁、解锁操作。
-(void)sellTicket{
self.totalTickets = 100;
for (NSInteger i = 0; i < 10; i++){
dispatch_async(dispatch_get_global_queue(0, 0), ^{
for (int i = 0; i < 10; i++) {
@synchronized (self) {
[self sellTicketMethord];
}
}
});
}
}
-(void)sellTicketMethord{
int oldTickets = self.totalTickets;
sleep(.5);
oldTickets -= 1;
self.totalTickets = oldTickets;
NSLog(@"当前剩余票数>>>>> %d", oldTickets);
}
atomic
atomic 原子属性。atomic 用于保证属性setter、getter的原子性操作,相当于在getter和setter内部加了线程同步的锁,它并不能保证使用属性的过程是线程安全的。
pthread_rwlock
pthread_rwlock:读写锁。需要导入头文件#import <pthread.h>。
注意事项:同一时间可以允许多个读线程。同一时间只允许一个写线程。同一时间只允许读或者写,不能同时进行。
常用方法
//初始化锁
pthread_rwlock_t lock;
pthread_rwlock_init(&_lock, NULL);
//读加锁
pthread_rwlock_rdlock(&_lock);
//读尝试加锁
pthread_rwlock_trywrlock(&_lock)
//写加锁
pthread_rwlock_wrlock(&_lock);
//写尝试加锁
pthread_rwlock_trywrlock(&_lock)
//解锁
pthread_rwlock_unlock(&_lock);
//销毁
pthread_rwlock_destroy(&_lock);
使用示例
1、导入头文件
#import <pthread.h>
2、全局属性
@property (assign, nonatomic) pthread_rwlock_t lock;
3、初始化
pthread_rwlock_init(&_lock, NULL);
4、使用
- (void)otherTest{
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);
}
dispatch_barrier_async
dispatch_barrier_async :栅栏函数
dispatch_semaphore_t signal = dispatch_semaphore_create(1);
dispatch_time_t overTime = dispatch_time(DISPATCH_TIME_NOW, 3 * NSEC_PER_SEC);
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
dispatch_semaphore_wait(signal, overTime);
NSLog(@"需要线程同步的操作1 开始");
sleep(2);
NSLog(@"需要线程同步的操作1 结束");
dispatch_semaphore_signal(signal);
});
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
sleep(1);
dispatch_semaphore_wait(signal, overTime);
NSLog(@"需要线程同步的操作2");
dispatch_semaphore_signal(signal);
});
网友评论