上篇文章介绍synchronized锁,今天介绍的是其他常用锁:NSLock,NSRecursiveLock ,NSCondition,NSConditionLock
锁的概念
锁的分类——互斥锁,自旋锁,读写锁
自旋锁
自旋锁是一种用于保护多线程共享资源的锁,与一般互斥锁(mutex)不同之处在于当它尝试获取锁时以忙等待(busy waiting)的形式不断地循环检查锁是否可用。当上一个线程的任务没有执行完毕的时候(被锁住),那么下一个线程会一直等待(不会睡眠),当上一个线程的任务执行完毕,下一个线程会立即执行。在多CPU的环境中,对持有锁较短的程序来说,使用自旋锁代替一般的互斥锁往往能够提高程序的性能。
优点:自旋锁不会引起调用者睡眠,所以不会进行线程调度、CPU时间片轮转等耗时操作。所有如果能在很短的时间内获得锁,自旋锁的效率远高于互斥锁
缺点:自旋锁一直占用CPU,他在未获得锁的情况下一直运行(自旋)占用着CPU,如果不能在很短的时间内获得锁,这无疑会使CPU效率降低
总结:效率高,但是一直占用CPU耗费资源,不能实现递归调用。
互斥锁
什么是互斥锁呢?
- 当上一个线程的任务没有执行完毕的时候(被锁住),那么下一个线程会进入睡眠状态等待任务执行完毕,此时CPU可以调度其他线程。当上一个线程的任务执行完毕,下一个线程会自动唤醒然后执行任务。
- 说到互斥后想到同步,同步是只有一个任务执行完了,下个任务才可以执行。
同步:互斥+顺序
常见的互斥锁:@synchronized
,NSLock
,pthread_mutex
,NSConditionLock
(条件锁),NSCondition
(条件锁),NSRecursiveLock
(递归锁)
以上概念转载自:https://juejin.cn/post/6999520963658268709/
锁的归类
条件锁:就是条件变量,当进程的某些资源要求不满足时就进入休眠,也就是锁住了,当资源被分配到了,条件锁打开,进程继续运行
- NSCondition
- NSConditionLock
递归锁:就是同一个线程可以加锁N次而不会引发死锁
- NSRecursiveLock
- pthread_mutext(recursive)
信号量:是一种更高级的同步机制,互斥锁可以说是semaphore在仅取值0/1时的特例。信号量可以有更多的取值空间,用来实现更加复杂的同步,而不单单是线程间互斥。
- dispatch_semaphore
读写锁
读写锁实际是一种特殊的互斥锁,他把对共享资源的访问分为读者和写者,读者只有对共享资源的读权限,写着可以对共享资源做写处理。所以一般我们在读的时候不会去加互斥锁,允许多个读者进行访问,但是如果有遇到写者访问资源,这时候必须对资源进行加锁,等其访问结束后才能继续读/写
特性:
- 多个读者同时访问
- 读与写互斥
- 写和写互斥即单写
上面都是一些概念,接下来就开始详细分析常用的锁
NSLock,NSRecursiveLock
简单的锁,不能够递归嵌套也不能多线程
- (void)testRecursive{
NSLock *lock = [[NSLock alloc] init];
for (int i= 0; i<10; i++) {
dispatch_async(dispatch_get_global_queue(0, 0), ^{
static void (^testMethod)(int);
testMethod = ^(int value){
// [lock lock];
if (value > 0) {
NSLog(@"current value = %d",value);
testMethod(value - 1);
}
// [lock unlock];
};
[lock lock];
testMethod(10);
[lock unlock];
});
}
}
输出:
这边我们的锁直接加在testMethod(10);
等线程这边递归完成后再继续下个线程,但是有时候我们可能会加在递归函数里面,如上面的注释,那就会造成死锁,为什么会造成死锁呢?在testMethod刚进去加锁,但是testMethod还没出来又嵌套进入到testMethod里又加锁,就造成了死锁,解决办法就是使用嵌套锁NSRecursiveLock/synchronized
- (void)testRecursive{
NSLock *lock = [[NSLock alloc] init];
for (int i= 0; i<10; i++) {
dispatch_async(dispatch_get_global_queue(0, 0), ^{
static void (^testMethod)(int);
testMethod = ^(int value){
[self.recursiveLock lock];
if (value > 0) {
NSLog(@"current value = %d -- %@",value, [NSThread currentThread]);
testMethod(value - 1);
}
[self.recursiveLock unlock];
};
testMethod(10);
});
}
}
输出结果:
current value = 10 -- {number = 3, name = (null)}
current value = 9 -- {number = 3, name = (null)}
current value = 8 -- {number = 3, name = (null)}
current value = 7 -- {number = 3, name = (null)}
current value = 6 -- {number = 3, name = (null)}
current value = 5 -- {number = 3, name = (null)}
current value = 4 -- {number = 3, name = (null)}
current value = 3 -- {number = 3, name = (null)}
current value = 2 -- {number = 3, name = (null)}
current value = 1 -- {number = 3, name = (null)}
这边使用NSRecursiveLock
尝试解决,但是很遗憾还是崩,只是崩溃的不一样,最后输出的结果如上图,为什么呢?因为NSRecursiveLock
不支持多线程,如果只开一个线程就不会崩,但是换成synchronized就OK
- (void)testRecursive{
NSLock *lock = [[NSLock alloc] init];
for (int i= 0; i<2; i++) {
dispatch_async(dispatch_get_global_queue(0, 0), ^{
static void (^testMethod)(int);
testMethod = ^(int value){
@synchronized (self) {
if (value > 0) {
NSLog(@"current value = %d -- %@",value, [NSThread currentThread]);
testMethod(value - 1);
}
}
};
testMethod(10);
});
}
}
输出结果:
current value = 10 -- {number = 4, name = (null)}
current value = 9 -- {number = 4, name = (null)}
current value = 8 -- {number = 4, name = (null)}
current value = 7 -- {number = 4, name = (null)}
current value = 6 -- {number = 4, name = (null)}
current value = 5 -- {number = 4, name = (null)}
current value = 4 -- {number = 4, name = (null)}
current value = 3 -- {number = 4, name = (null)}
current value = 2 -- {number = 4, name = (null)}
current value = 1 -- {number = 4, name = (null)}
current value = 10 -- {number = 5, name = (null)}
current value = 9 -- {number = 5, name = (null)}
current value = 8 -- {number = 5, name = (null)}
current value = 7 -- {number = 5, name = (null)}
current value = 6 -- {number = 5, name = (null)}
current value = 5 -- {number = 5, name = (null)}
current value = 4 -- {number = 5, name = (null)}
current value = 3 -- {number = 5, name = (null)}
current value = 2 -- {number = 5, name = (null)}
current value = 1 -- {number = 5, name = (null)}
接下来我们来看看NSLock
和NSRecursiveLock
的源码,因为NSLock
和NSRecursiveLock
是在Foundation
框架,这个是不开源的但是swift
的Foundation
框架是开源的,我们可以看看swift
的Foundation
框架
通过源码就知道NSLock就是对mutex的封装
这个也是对mutex的封装,但是在初始化跟NSlock有所不同,
pthread_mutexattr_settype(attrs, Int32(PTHREAD_MUTEX_RECURSIVE))
添加了嵌套属性
NSCondition和NSConditionLock
NSCondition条件锁主要运用在生产着消费者模型
- (void)testConditon{
_testCondition = [[NSCondition alloc] init];
//创建生产-消费者
for (int i = 0; i < 50; i++) {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
[self lg_producer];
});
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
[self lg_consumer];
});
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
[self lg_consumer];
});
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
[self lg_producer];
});
}
}
- (void)producer{
[_testCondition lock]; // 操作的多线程影响
self.ticketCount = self.ticketCount + 1;
NSLog(@"生产一个 现有 count %zd",self.ticketCount);
[_testCondition signal]; // 信号
[_testCondition unlock];
}
- (void)consumer{
[_testCondition lock]; // 操作的多线程影响
if (self.ticketCount == 0) {
NSLog(@"等待 count %zd",self.ticketCount);
[_testCondition wait];
}
//注意消费行为,要在等待条件判断之后
self.ticketCount -= 1;
NSLog(@"消费一个 还剩 count %zd ",self.ticketCount);
[_testCondition unlock];
}
当消费者消费到没有物品时就会处于等待,而生产者每生产一个产品都会通知一下消费者,让那些处于等待状态的消费者及时消费。
底层是对cond的封装
NSConditionLock 的使用
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];
sleep(0.1);
NSLog(@"线程 2");
[conditionLock unlockWithCondition:1];
});
dispatch_async(dispatch_get_global_queue(0, 0), ^{
[conditionLock lock];
NSLog(@"线程 3");
[conditionLock unlock];
});
输出:
线程 3
线程 2
线程 1
通过lockWhenCondition
控制线程的执行顺序,这边预先设置condition为2,因为线程2的条件是[conditionLock lockWhenCondition:2]
满足条件会比线程1优先执行,执行完[conditionLock unlockWithCondition:1];
设置为1,这样线程1就满足条件,等待cpu调度
。
读写锁
之前介绍的读写锁,多个线程可以同时对资源进行读操作不用加锁,但是写操作是需要加锁。这样我们就可以通过GCD的栅栏函数来实现
- (id)init{
self = [super init];
if (self){
// 创建一个并发队列:
self.concurrent_queue = dispatch_queue_create("read_write_queue", DISPATCH_QUEUE_CONCURRENT);
// 创建数据字典:
self.dataCenterDic = [NSMutableDictionary dictionary];
}
return self;
}
- (void)getObjc {
for (int i=0; i<5; i++) {
dispatch_async(self.concurrent_queue, ^{
// 多个线程读取数据
[self objectForKey:@"a"];
});
}
}
#pragma mark - 读数据
- (id)objectForKey:(NSString *)key{
__block id obj;
// 同步读取指定数据:
dispatch_sync(self.concurrent_queue, ^{
obj = [self.dataCenterDic objectForKey:key];
});
return obj;
}
#pragma mark - 写数据
- (void)setObject:(id)obj forKey:(NSString *)key{
// 异步栅栏调用设置数据:
dispatch_barrier_async(self.concurrent_queue, ^{
[self.dataCenterDic setObject:obj forKey:key];
});
}
注意:同步读取指定数据之所以要加dispatch_sync
是为了满足读和写的互斥