iOS 探索系列相关文章 :
接上文对 iOS
中的一些锁来进行分析, 前面主要分析了 iOS
中的 @synchronized
锁的实现和相关问题, 接下来对其他的一些锁来进行分析
1. NSLock
1. 实现分析
NSLock
在开发中也算是比较常见的锁了, 它是一个 非递归互斥锁 。
@protocol NSLocking
- (void)lock; // 加锁
- (void)unlock; // 解锁
@end
@interface NSLock : NSObject <NSLocking> {
@private
void *_priv;
}
- (BOOL)tryLock; // 尝试加锁
- (BOOL)lockBeforeDate:(NSDate *)limit; // 带着截止日期的加锁
// 锁的名字
@property (nullable, copy) NSString *name API_AVAILABLE(macos(10.5), ios(2.0), watchos(2.0), tvos(9.0));
@end
NSlock
的底层实现是在 Foundation
框架下的, 没有开源; 但是 Swift
的 Foundation
已经开源了, 所以可以大致了解一下先:
#if os(Windows)
private typealias _MutexPointer = UnsafeMutablePointer<SRWLOCK>
private typealias _RecursiveMutexPointer = UnsafeMutablePointer<CRITICAL_SECTION>
private typealias _ConditionVariablePointer = UnsafeMutablePointer<CONDITION_VARIABLE>
#elseif CYGWIN
private typealias _MutexPointer = UnsafeMutablePointer<pthread_mutex_t?>
private typealias _RecursiveMutexPointer = UnsafeMutablePointer<pthread_mutex_t?>
private typealias _ConditionVariablePointer = UnsafeMutablePointer<pthread_cond_t?>
#else
private typealias _MutexPointer = UnsafeMutablePointer<pthread_mutex_t>
private typealias _RecursiveMutexPointer = UnsafeMutablePointer<pthread_mutex_t>
private typealias _ConditionVariablePointer = UnsafeMutablePointer<pthread_cond_t>
#endif
在源码里面可以看到有 mutex
互斥锁相关的字眼, 这里就不过多研究了, 有兴趣的可以自行了解一下, 接下来来看看它的使用方面的一些东西。
2. 使用
1. 正常使用
- (void)nslock_Text {
NSLock *lock = [[NSLock alloc] init];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[lock lock];
NSLog(@"线程 1 开始");
sleep(5);
NSLog(@"线程 1 完成");
[lock unlock];
});
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[lock lock];
NSLog(@"线程 2 开始");
sleep(2);
NSLog(@"线程 2 完成");
[lock unlock];
});
}
// 结果
线程 1 开始
线程 1 完成
线程 2 开始
线程 2 完成
线程 1 持有锁开始执行, 线程 2 此时就无法继续持有锁了进入休眠状态, 等待线程 1 执行完毕以后线程 2 唤醒后持有锁开始执行。
2. 某些场景存在的问题
- (void)test {
NSLock *lock = [[NSLock alloc] init];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
static void (^block)(int);
block = ^(int value) {
NSLog(@"加锁 --------");
[lock lock];
NSLog(@"加锁成功 ----");
if (value > 0) {
NSLog(@"value = %d", value);
block(value - 1);
}
NSLog(@"解锁 --------");
[lock unlock];
};
block(10);
});
}
// 执行结果
加锁 -------
加锁成功 ----
value = 10
加锁 -------
看到上面的结果了吗, 第一次加锁加锁成功后在 block 块内进行了递归操作, 递归操作再次来到加锁时因为前面的过程还没有解锁, 所以就不能再次加锁成功, 就导致了线程堵塞。
出现问题的原因就是因为 NSLock
是一个 非递归锁 , 如果要递归操作的话, 可以使用接下来要介绍的锁 NSRecursiveLock
。
接下来的几个锁就直接看看使用方法和存在的问题吧, 因为确实没有专门去研究实现什么的
2. NSRecursiveLock
1. 使用
// 把上面的 NSLock 换成 NSRecursiveLock
- (void)test2 {
NSRecursiveLock *lock = [[NSRecursiveLock alloc] init];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
static void (^block)(int);
block = ^(int value) {
NSLog(@"加锁 --------");
[lock lock];
NSLog(@"加锁成功 ----");
if (value > 0) {
NSLog(@"value = %d", value);
block(value - 1);
}
NSLog(@"解锁 --------");
[lock unlock];
};
block(10);
});
}
// 结果
运行正常
把上面 NSLock
出现问题的情况换成 NSRecursiveLock
后就没有问题了, NSRecursiveLock
允许在同一个线程中重复加锁。NSRecursiveLock
会记录加锁和解锁的次数, 当两者平衡的时候才会释放掉锁, 才允许其他线程上锁成功。
2. 存在的问题
那么如果在多个线程情况下呢, NSRecursiveLock
还管用吗?
// 多条线程下
- (void)test3 {
NSRecursiveLock *lock = [[NSRecursiveLock alloc] init];
for (int i = 0; i < 10; i++) {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
static void (^block)(int);
block = ^(int value) {
NSLog(@"加锁 --------");
[lock lock];
NSLog(@"加锁成功 ----");
if (value > 0) {
NSLog(@"value = %d", value);
block(value - 1);
}
NSLog(@"解锁 --------");
[lock unlock];
};
block(10);
});
}
}
// 执行结果
// 程序崩溃, 出现野指针错误
// -[__NSCFType lock]: unrecognized selector sent to instance 0x600000dc1d40
原因分析:
线程 1 在执行中对对象进行了多次加锁, 然后线程 2 在执行中又对对象进行了多次加锁, 最终导致了两个线程直接相互等待对方解锁, 就导致了 死锁问题 。
如何解决呢?
可以使用之前分析过的
@synthronized
来进行解决, 因为它会进行缓存查找。如果该对象已经存在缓存中, 它是对他的加锁数量进行的变更, 并不会产生新的锁。
3. NSCondition
条件锁平时使用的不是很多, 与信号量相似, 就是当线程执行到锁只有需要满足一定的条件才能持有锁, 否则就会阻塞等待直到条件满足。
- (void)testCondition1 {
NSCondition *lock = [[NSCondition alloc] init];
NSMutableArray *array = [NSMutableArray array];
//
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[lock lock];
if (!array.count) {
[lock wait];
}
[array removeAllObjects];
NSLog(@"array removeAllObjects");
[lock unlock];
});
//
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
sleep(1);
[lock lock];
[array addObject:@1];
NSLog(@"array add 1");
[lock signal];
NSLog(@"发送信号");
[lock unlock];
});
}
NSCondition
可以对多个线程同时进行加锁操作, 当在加锁线程中调用wait
方法后, 可以是线程进入休眠状态等待唤起的信号- 在其他线程中通过调用
signal
或者broadcast
方法可以唤醒已经休眠的线程, 不同点在于signal
只能唤起一个休眠的线程, 而broadcast
则可以唤醒所有休眠的线程。
4. NSConditionLock
@interface NSConditionLock : NSObject <NSLocking> {
@private
void *_priv;
}
- (instancetype)initWithCondition:(NSInteger)condition NS_DESIGNATED_INITIALIZER;
@property (readonly) NSInteger condition;
- (void)lockWhenCondition:(NSInteger)condition;
- (BOOL)tryLock;
- (BOOL)tryLockWhenCondition:(NSInteger)condition;
- (void)unlockWithCondition:(NSInteger)condition;
- (BOOL)lockBeforeDate:(NSDate *)limit;
- (BOOL)lockWhenCondition:(NSInteger)condition beforeDate:(NSDate *)limit;
@property (nullable, copy) NSString *name API_AVAILABLE(macos(10.5), ios(2.0), watchos(2.0), tvos(9.0));
@end
NSConditionLock
和 NSLock
的使用方式比较类似, 只是在获取锁的时候增加了一个条件限制, 然后多了一些针对条件 condition
的操作方法。下面来看看它的使用方法:
//
- (void)testConditonLock{
// 信号量
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];
});
}
// 结果
// 线程 2 或者 3 首先执行
// 线程 1 的执行一定会在 2 之后
// 如果先执行的 2, 3 和 1 的先后要看顺序
下面就主要看看里面的加锁方法吧:
lock
同NSLock
一样, 不需要判断条件, 如果可以的话就直接获取锁lockWhenCondition:
如果没有其他线程获取锁, 并且条件满足就能够获取锁, 如果条件不满足, 需要等待直到条件满足后才能获取锁unlockWithCondition:
释放锁, 同时把条件condition
设置到想要的条件
condition 是一个整数值
5. 各种互斥锁的性能对比图
下面来放上一张各个所之间性能的对比图:
关于 dispatch_semapore 和 alloc 会放到后面的文章专门研究一下
可以看到效率方面 OSSpinLock
的效率是最高的, 虽然它现在已经被弃用了。然而经常使用的 @synchronize
的效率是最差的。