版本记录
版本号 | 时间 |
---|---|
V1.0 | 2017.05.21 |
前言
ios中有好几种锁,比如自旋锁,互斥锁,信号量等等,锁其实是多线程数据安全的一种解决方案,作用就是保证同一时间只有一个线程访问和改变某些敏感数据,这些锁的性能也是差别很大,最近看了几个技术大牛的技术博客,我才发现我以前对锁的理解太肤浅了,心虚的赶紧找资料又开始了深入学习,然后整理出来。前面介绍了几种锁:
1. ios开发中的几种锁(一)
2. ios开发中的几种锁(二)
这篇接着讲其他的几种锁。
详情
一、NSLock普通锁
我们先看一下NSLock的API。
@interface NSLock : NSObject <NSLocking> {
@private
void *_priv;
}
- (BOOL)tryLock;
- (BOOL)lockBeforeDate:(NSDate *)limit;
@property (nullable, copy) NSString *name NS_AVAILABLE(10_5, 2_0);
@end
先解释一下这几个参数
- lock、unlock:不多做解释,和上面一样。
- trylock:能加锁返回 YES 并执行加锁操作,相当于 lock,反之返回 NO。
- lockBeforeDate:这个方法表示会在传入的时间内尝试加锁,若能加锁则执行加锁操作并返回 YES,反之返回 NO。
然后我们看代码
1. JJNSlockVC.h
#import <UIKit/UIKit.h>
@interface JJNSlockVC : UIViewController
@end
2. JJNSlockVC.m
#import "JJNSlockVC.h"
#import <Foundation/Foundation.h>
@interface JJNSlockVC ()
@end
@implementation JJNSlockVC
#pragma mark - Override Base Function
- (void)viewDidLoad
{
[super viewDidLoad];
[self aboutNSlock];
}
#pragma mark - Object Private Function
- (void)aboutNSlock
{
NSLock *lock = [NSLock new];
int __block num = 10;
//线程1
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSLog(@"线程1 尝试加速ing...");
[lock lock];
num = num + 1;
NSLog(@"num1=%d",num);
NSLog(@"线程1");
[lock unlock];
NSLog(@"线程1解锁成功");
});
//线程2
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSLog(@"线程2 尝试加速ing...");
BOOL x = [lock lockBeforeDate:[NSDate dateWithTimeIntervalSinceNow:4]];
if (x) {
NSLog(@"线程2");
num = num + 1;
NSLog(@"num2=%d",num);
[lock unlock];
}else{
NSLog(@"失败");
}
});
}
@end
看一下运行结果
2017-05-21 18:06:30.005 lock[1726:81709] 线程2 尝试加速ing...
2017-05-21 18:06:30.005 lock[1726:81708] 线程1 尝试加速ing...
2017-05-21 18:06:30.008 lock[1726:81709] 线程2
2017-05-21 18:06:30.009 lock[1726:81709] num2=11
2017-05-21 18:06:30.010 lock[1726:81708] num1=12
2017-05-21 18:06:30.010 lock[1726:81708] 线程1
2017-05-21 18:06:30.011 lock[1726:81708] 线程1解锁成功
二、NSCondition
同上我们还是先看下API
@interface NSCondition : NSObject <NSLocking> {
@private
void *_priv;
}
- (void)wait;
- (BOOL)waitUntilDate:(NSDate *)limit;
- (void)signal;
- (void)broadcast;
@property (nullable, copy) NSString *name NS_AVAILABLE(10_5, 2_0);
@end
看一下参数
- wait:进入等待状态
- waitUntilDate::让一个线程等待一定的时间
- signal:唤醒一个等待的线程
- broadcast:唤醒所有等待的线程
看一下代码
1. 线程延迟3s
1. JJNSConditionVC.h
#import <UIKit/UIKit.h>
@interface JJNSConditionVC : UIViewController
@end
2. JJNSConditionVC.m
#import "JJNSConditionVC.h"
#import <Foundation/Foundation.h>
@interface JJNSConditionVC ()
@end
@implementation JJNSConditionVC
#pragma mark - Override Base Function
- (void)viewDidLoad
{
[super viewDidLoad];
[self aboutNSConditionLockWaite3s];
}
#pragma mark - Object Private Function
- (void)aboutNSConditionLockWaite3s
{
NSCondition *cLock = [NSCondition new];
int __block num = 10;
//线程1
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSLog(@"start");
[cLock lock];
[cLock waitUntilDate:[NSDate dateWithTimeIntervalSinceNow:3]];
num = num + 1;
NSLog(@"num1=%d",num);
NSLog(@"线程1");
[cLock unlock];
});
}
@end
下面看一下输出结果
2017-05-21 18:42:08.611 lock[2112:105644] start
2017-05-21 18:42:11.684 lock[2112:105644] num1=11
2017-05-21 18:42:11.685 lock[2112:105644] 线程1
2. 唤醒等待线程
看代码
1. JJNSConditionVC.m
#import "JJNSConditionVC.h"
#import <Foundation/Foundation.h>
@interface JJNSConditionVC ()
@end
@implementation JJNSConditionVC
#pragma mark - Override Base Function
- (void)viewDidLoad
{
[super viewDidLoad];
//延迟等待3s
// [self aboutNSConditionLockWaite3s];
//唤醒一个线程
[self weakAWaitingThread];
}
#pragma mark - Object Private Function
- (void)weakAWaitingThread
{
NSCondition *cLock = [NSCondition new];
int __block num = 10;
//线程1
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[cLock lock];
NSLog(@"线程1加锁成功");
[cLock wait];
NSLog(@"线程1--%@",[NSThread currentThread]);
num = num + 1;
NSLog(@"num1=%d",num);
[cLock unlock];
});
//线程2
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[cLock lock];
NSLog(@"线程2加锁成功");
[cLock wait];
NSLog(@"线程2--%@",[NSThread currentThread]);
num = num + 1;
NSLog(@"num2=%d",num);
[cLock unlock];
});
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSLog(@"唤醒一个等待的线程");
[cLock signal];
});
}
@end
看输出结果
2017-05-21 18:54:06.471 lock[2294:113847] 线程2加锁成功
2017-05-21 18:54:06.471 lock[2294:113844] 唤醒一个等待的线程
2017-05-21 18:54:06.471 lock[2294:113845] 线程1加锁成功
2017-05-21 18:54:06.472 lock[2294:113847] 线程2--<NSThread: 0x6080002632c0>{number = 3, name = (null)}
2017-05-21 18:54:06.473 lock[2294:113847] num2=11
由上可知,线程2被唤醒了,进行了计算。
3. 唤醒所有线程
看代码
1. JJNSConditionVC.m
#import "JJNSConditionVC.h"
#import <Foundation/Foundation.h>
@interface JJNSConditionVC ()
@end
@implementation JJNSConditionVC
#pragma mark - Override Base Function
- (void)viewDidLoad
{
[super viewDidLoad];
//延迟等待3s
// [self aboutNSConditionLockWaite3s];
//唤醒一个线程
// [self weakAWaitingThread];
//唤醒所有线程
[self weakAllThread];
}
#pragma mark - Object Private Function
- (void)weakAllThread
{
NSCondition *cLock = [NSCondition new];
int __block num = 10;
//线程1
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[cLock lock];
NSLog(@"线程1加锁成功");
[cLock wait];
NSLog(@"线程1--%@",[NSThread currentThread]);
num = num + 1;
NSLog(@"num1=%d",num);
[cLock unlock];
});
//线程2
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[cLock lock];
NSLog(@"线程2加锁成功");
[cLock wait];
NSLog(@"线程2--%@",[NSThread currentThread]);
num = num + 1;
NSLog(@"num2=%d",num);
[cLock unlock];
});
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSLog(@"唤醒所有等待的线程");
[cLock broadcast];
});
}
@end
看结果输出
2017-05-21 19:04:11.191 lock[2433:121014] 线程1加锁成功
2017-05-21 19:04:11.191 lock[2433:121017] 唤醒所有等待的线程
2017-05-21 19:04:11.192 lock[2433:121035] 线程2加锁成功
2017-05-21 19:04:11.192 lock[2433:121014] 线程1--<NSThread: 0x600000266400>{number = 3, name = (null)}
2017-05-21 19:04:11.192 lock[2433:121014] num1=11
因为我把三段代码都放在异步执行,这里线程1开始执行并处于等待状态,然后就执行唤醒所有的线程,这个以后线程2才开始进行等待状态,所以这个时候的解锁所有的线程,其实只能解锁线程1的执行,输出结果是正确的。
三、NSLock在递归锁中的错误演示
看代码
#import "JJNSlockVC.h"
#import <Foundation/Foundation.h>
@interface JJNSlockVC ()
@end
@implementation JJNSlockVC
#pragma mark - Override Base Function
- (void)viewDidLoad
{
[super viewDidLoad];
// [self aboutNSlock];
//NSLock在递归锁中的错误演示
[self nslockErrorDisplayInRecresive];
}
- (void)nslockErrorDisplayInRecresive
{
NSLock *nsLock = [[NSLock alloc] init];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
static void (^RecursiveBlock)(int);
RecursiveBlock = ^(int value) {
[nsLock lock];
if (value > 0) {
NSLog(@"线程%d", value);
RecursiveBlock(value - 1);
}
[nsLock unlock];
};
RecursiveBlock(4);
});
}
@end
看输出结果
2017-05-21 19:18:58.723 lock[2606:130260] 线程4
2017-05-21 19:18:58.726 lock[2606:130260] *** -[NSLock lock]: deadlock (<NSLock: 0x6000000dbdd0> '(null)')
2017-05-21 19:18:58.727 lock[2606:130260] *** Break on _NSLockError() to debug.
上面这段代码是一个典型的死锁情况。在我们的线程中,RecursiveMethod 是递归调用的。所以每次进入这个 block 时,都会去加一次锁,而从第二次开始,由于锁已经被使用了且没有解锁,所以需要等待锁被解除,这样就导致了死锁,线程被阻塞住了。
下面我们将锁换位递归锁,再次尝试
// NSLock *nsLock = [[NSLock alloc] init];
NSRecursiveLock * nsLock = [[NSRecursiveLock alloc] init];
让我们再次run起来,查看结果
2017-05-21 19:22:46.774 lock[2669:133395] 线程4
2017-05-21 19:22:46.775 lock[2669:133395] 线程3
2017-05-21 19:22:46.777 lock[2669:133395] 线程2
2017-05-21 19:22:46.777 lock[2669:133395] 线程1
就会发现,线程锁可是递归,而不会锁死了。
相关参考技术博客
1.iOS 开发中的八种锁(Lock)
2.不再安全的 OSSpinLock
3. NSRecursiveLock递归锁的使用
4.关于dispatch_semaphore的使用
5.实现锁的多种方式和锁的高级用法
后记
未完,待续,谢谢大家~~~