有一张图片简单的比较了各种锁的加解锁性能:
先介绍锁的感念
可以理解为加锁中间的一段代码在多线程下只能按串行队列来执行,防止发生资源的抢夺。
自旋锁和互斥锁的区别
自旋锁会忙等:
所谓忙等,即在访问被锁资源时,调用者线程不会休眠,而是不停循环在那里,直到被锁资源释放锁。
互斥锁会休眠:
所谓休眠,即在访问被锁资源时,调用者线程会休眠,此时cpu可以调度其他线程工作。直到被锁资源释放锁。此时会唤醒休眠线程。
优缺点:
自旋锁的优点在于,因为自旋锁不会引起调用者睡眠,所以不会进行线程调度,cpu时间片轮转等耗时操作。所有如果能在很短的时间内获得锁,自旋锁的效率远高于互斥锁。
缺点在于,自旋锁一直占用CPU,他在未获得锁的情况下,一直运行--自旋,所以占用着CPU,如果不能在很短的时 间内获得锁,这无疑会使CPU效率降低。自旋锁不能实现递归调用。
NSLock的简单使
//主线程中
NSLock *lock = [[NSLock alloc] init];
//线程1
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[lock lock];
NSLog(@"线程1");
sleep(1);
[lock unlock];
NSLog(@"线程1解锁成功");
});
//线程2
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
sleep(1);//以保证让线程2的代码后执行
[lock lock];
NSLog(@"线程2");
[lock unlock];
});
打印日志
注意:NSLock虽然是互斥锁,但是内部有优化,前期会已自旋的方式尝试1s左右去加锁,如果还是没加锁成功才会休眠
NSConditionLock的简单使
1.lock 不分条件,如果锁没被申请,直接执行代码
- unlock不会清空条件,之后满足条件的锁还会执行
3.unlockWithCondition:我的理解就是设置解锁条件(同一时刻只有一个条件,如果已设置条件,相当于修改条件)
lockWhenCondition:满足特定条件,执行相应代码
//主线程中
NSConditionLock *lock = [[NSConditionLock alloc] initWithCondition:0];
//线程1
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[lock lockWhenCondition:1];
NSLog(@"线程1");
sleep(2);
[lock unlock];
});
//线程2
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
sleep(1);//以保证让线程2的代码后执行
if ([lock tryLockWhenCondition:0]) {
NSLog(@"线程2");
[lock unlockWithCondition:2];
NSLog(@"线程2解锁成功");
}
else {
NSLog(@"线程2尝试加锁失败");
}
});
//线程3
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// sleep(2);//以保证让线程2的代码后执行
if ([lock tryLockWhenCondition:2]) {
NSLog(@"线程3");
[lock unlock];
NSLog(@"线程3解锁成功");
}
else {
NSLog(@"线程3尝试加锁失败");
}
});
//线程4
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// sleep(3);//以保证让线程2的代码后执行
[lock lockWhenCondition:2];
NSLog(@"线程4");
[lock unlockWithCondition:1];
NSLog(@"线程4解锁成功");
});
打印结果
2018-04-17 16:59:26.995255+0800 NSLock测试[48379:5945905] 线程3尝试加锁失败
2018-04-17 16:59:28.000120+0800 NSLock测试[48379:5945903] 线程2
2018-04-17 16:59:28.000791+0800 NSLock测试[48379:5945903] 线程2解锁成功
2018-04-17 16:59:28.000851+0800 NSLock测试[48379:5945904] 线程4
2018-04-17 16:59:28.001999+0800 NSLock测试[48379:5945904] 线程4解锁成功
2018-04-17 16:59:28.002044+0800 NSLock测试[48379:5945902] 线程1
结果说明:
1 初始化一个条件锁,条件为0
2 由于线程1 和线程4条件不满足,所以循环一段时间休眠,等待满足条件满足时唤醒;线程3尝试加锁,不会阻塞线程,但是条件不满足所以直接休眠;线程2休眠1秒后尝试加锁。满足条件所以加锁成功;
3 线程2伴随重置加锁条件2进行解锁;
4 此时线程4满足条件,系统唤醒进行加锁,并且重置加锁条件14
5 此时线程1满足条件,系统唤醒进行加锁,并且解锁,此时条件为1
NSRecursiveLock递归锁
同一个线程可以多次加锁
NSLock *lock = [[NSLock alloc] init];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
static void (^RecursiveMethod)(int);
RecursiveMethod = ^(int value) {
[lock lock];
if (value > 0) {
NSLog(@"value = %d", value);
sleep(2);
RecursiveMethod(value - 1);
}
[lock unlock];
};
RecursiveMethod(5);
});
这段代码是一个典型的死锁情况。在我们的线程中,RecursiveMethod是递归调用的。所以每次进入这个block时,都会去加一次锁,而从第二次开始,由于锁已经被使用了且没有解锁,所以它需要等待锁被解除,这样就导致了死锁,线程被阻塞住了
正确写法
NSRecursiveLock *lock = [[NSRecursiveLock alloc] init];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
static void (^RecursiveMethod)(int);
RecursiveMethod = ^(int value) {
[lock lock];
if (value > 0) {
NSLog(@"value = %d", value);
sleep(2);
RecursiveMethod(value - 1);
}
[lock unlock];
};
RecursiveMethod(5);
});
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
sleep(2);
BOOL flag = [lock lockBeforeDate:[NSDate dateWithTimeIntervalSinceNow:1]];
if (flag) {
NSLog(@"lock before date");
[lock unlock];
} else {
NSLog(@"fail to lock before date");
}
});
value = 5
value = 4
fail to lock before date
value = 3
value = 2
value = 1
GCD的读写锁实现
#import "MyObject.h"
@interface MyObject()
@property(nonatomic,copy)NSString *testString;
@property(nonatomic,strong)dispatch_queue_t syncQueue;
@end
@implementation MyObject{
NSString *_testString;
}
@synthesize testString=_testString;
//在并发队列里同步读取属性值
- (NSString *)testString{
__block NSString *str;
dispatch_sync(self.syncQueue, ^{
str=_testString;
});
return str;
}
//异步设置属性值
- (void)setTestString:(NSString *)testString{
//执行此操作时队列其他操作等待
//这样可同时有多个线程读取该属性,同一时刻只能有一个线程写值且读线程等到
dispatch_barrier_async(self.syncQueue, ^{
_testString=testString;
});
}
- (dispatch_queue_t)syncQueue{
if(!_syncQueue){
//这里使用全局并发队列
_syncQueue=dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
}
return _syncQueue;
}
参考文章
https://bestswifter.com/ios-lock/#osspinlock
//www.greatytc.com/u/f8d737319346
锁的简单实现文章
//www.greatytc.com/p/a33959324cc7