前言
这篇文章,记录几种锁的简单应用。
@synchronized
使用起来最简单的一个锁,直接将要锁定的代码用@synchronized
包裹,如下:
- (void)demo33
{
for (int i = 0; i < 100000; i++) {
dispatch_async(dispatch_get_global_queue(0, 0), ^{
@synchronized (self) {
self.testArray = [NSMutableArray array];
}
});
}
}
需要注意的是:
-
@synchronized
的参数在使用期间不能为nil
- 性能问题
NSLock
NSLock
互斥锁的一种,性能高于@synchronized
,使用也比较简单
- (void)demo33
{
NSLock *lock = [[NSLock alloc]init];
for (int i = 0; i < 100000; i++) {
dispatch_async(dispatch_get_global_queue(0, 0), ^{
[lock lock];
self.testArray = [NSMutableArray array];
[lock unlock];
});
}
}
注意,当存在嵌套递归的情况时,比如下面的代码:
NSLock *lock = [[NSLock alloc] init];
for (int i= 0; i<100; 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];
};
testMethod(10);
});
}
这里执行时会发现有问题了,当在嵌套递归的情况下,单个线程内的lock
会发生死锁;而线程与线程之间发生循环等待任务;从而导致无法正常执行下去,那么接下看另外一个锁。
NSRecursiveLock
从类名上看,NSRecursiveLock
是一个递归锁,我们用上面的案例,然后替换使用NSRecursiveLock
看下:
- (void)demo44
{
NSRecursiveLock *recursiveLock = [[NSRecursiveLock alloc] init];
for (int i= 0; i<100; i++) {
dispatch_async(dispatch_get_global_queue(0, 0), ^{
static void (^testMethod)(int);
testMethod = ^(int value){
[recursiveLock lock];
if (value > 0) {
NSLog(@"current value = %d",value);
testMethod(value - 1);
}
[recursiveLock unlock];
};
testMethod(10);
});
}
}
执行后发现:
2020-11-10 17:39:09.416482+0800 TestApp[47016:8556099] current value = 10
2020-11-10 17:39:09.416671+0800 TestApp[47016:8556099] current value = 9
2020-11-10 17:39:09.416811+0800 TestApp[47016:8556099] current value = 8
2020-11-10 17:39:09.416952+0800 TestApp[47016:8556099] current value = 7
2020-11-10 17:39:09.417089+0800 TestApp[47016:8556099] current value = 6
2020-11-10 17:39:09.417214+0800 TestApp[47016:8556099] current value = 5
2020-11-10 17:39:09.417338+0800 TestApp[47016:8556099] current value = 4
2020-11-10 17:39:09.417461+0800 TestApp[47016:8556099] current value = 3
2020-11-10 17:39:09.417590+0800 TestApp[47016:8556099] current value = 2
2020-11-10 17:39:09.417858+0800 TestApp[47016:8556099] current value = 1
这里由于我们的
[recursiveLock lock];
位置不对,导致线程之间发生循环等待,从而导致线程间的死锁,我们调整下lock
的位置:
- (void)demo44
{
NSRecursiveLock *recursiveLock = [[NSRecursiveLock alloc] init];
for (int i= 0; i<100; i++) {
dispatch_async(dispatch_get_global_queue(0, 0), ^{
static void (^testMethod)(int);
[recursiveLock lock];
testMethod = ^(int value){
if (value > 0) {
NSLog(@"current value = %d",value);
testMethod(value - 1);
}
[recursiveLock unlock];
};
testMethod(10);
});
}
}
然后执行结果正常。这里也就说明一个坑点就是,尽管NSRecursiveLock
是一把支持递归嵌套场景的锁,但是如果使用不当还是会出现各种问题,所以这里的建议是如果业务逻辑简单,直接使用@synchronized
锁(使用简单,支持多线程递归嵌套);如果要使用NSRecursiveLock
(性能高),就要注意使用的细节了。
NSCondition
iOS下一个简单的条件锁,当进程的某些资源要求不满足时就进入休眠,也就
是锁住了。当资源被分配到了,条件锁打开,进程继续运行。这里只做一些简单的使用。条件锁即生产者和消费者的关系,比如卖包子和买包子,当前卖家有包子可卖,买包子的就可以正常买;当包子卖完时,此时卖家暂停卖包子,开始蒸包子,买家也暂停买包子,开始等待;等卖家包子蒸好了,卖家通知买家可以买了,买家就能继续买包子了。
来看一个案例:
- (void)lg_testConditon{
_testCondition = [[NSCondition alloc] init]; //条件锁
//创建生产-消费者
for (int i = 0; i < 50; i++) {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
[self producer]; //蒸包子
});
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
[self consumer]; //买包子
});
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
[self consumer]; //买包子
});
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
[self producer]; //蒸包子
});
}
}
- (void)producer{
[_testCondition lock]; // 操作的多线程影响
self.bunCount = self.bunCount + 1;
NSLog(@"蒸好一个 现有 count %zd",self.bunCount);
[_testCondition signal]; // 信号,卖家说可以买了
[_testCondition unlock];
}
- (void)consumer{
[_testCondition lock]; // 操作的多线程影响
if (self.bunCount == 0) { //没包子了 买家进入等待(即等待signal信号)
NSLog(@"等待 count %zd",self.bunCount);
[_testCondition wait];
}
//注意消费行为,要在等待条件判断之后 开始买
self.bunCount -= 1;
NSLog(@"消费一个 还剩 count %zd ",self.bunCount);
[_testCondition unlock];
}
这里要注意的是由于多线程,这里无论是买还是卖都要加锁(非常贴进生活嘛),由于每次lock
,unlock
,signal
,wait
显得非常的繁琐,所以就衍生出了一个更加高级点的条件锁NSConditionLock
。
NSConditionLock
这个锁本质上跟NSCondition
是一致的,只是将之前的繁琐的操作给去掉了,对开发者更加的友好,通过一个condition
来控制执行。
#pragma mark -- NSConditionLock
- (void)testConditonLock{
// 信号量
NSConditionLock *conditionLock = [[NSConditionLock alloc] initWithCondition:2];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
[conditionLock lockWhenCondition:1]; //if condition == 1 执行
NSLog(@"任务 1");
[conditionLock unlockWithCondition:0];
});
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0), ^{
[conditionLock lockWhenCondition:2]; // if condition == 2 执行
sleep(0.1);
NSLog(@"任务 2");
[conditionLock unlockWithCondition:1]; // condition == 1
});
dispatch_async(dispatch_get_global_queue(0, 0), ^{
[conditionLock lock]; // 没有条件直接执行
NSLog(@"任务 3");
[conditionLock unlock];
});
}
这里其实就是在加锁的时候给了解锁条件,NSConditionLock
在初始化时给了一个默认的条件:condition = 2
, 这里的任务2满足条件,会执行;任务3不需要条件,也会执行;由于异步并发的原因,任务2和任务3的执行时没有顺序的,但任务1的执行就依赖于任务2,任务1 只有满足condition == 1
的时候才能执行,而这个需要等待任务2执行完后,调用[conditionLock unlockWithCondition:1];
将condition == 1
,然后发出broadcast
通知,让满足条件的任务去执行。
dispatch_semaphore_t
dispatch_semaphore_t
是GCD
下的信号量锁,这个锁还是比较常用的,特别是在一些异步取值同步返回的操作中,比如:
- (int)demo777
{
__block int result = 0;
dispatch_semaphore_t semphore = dispatch_semaphore_create(0);
dispatch_async(dispatch_get_global_queue(0, 0), ^{
sleep(2);
result = 1+0+2+4;
dispatch_semaphore_signal(semphore); //释放等待 value++
});
//进入等待 value--
dispatch_semaphore_wait(semphore, DISPATCH_TIME_FOREVER);
NSLog(@"result == %d",result);
return result;
}
当value
小于0时进入等待,即dispatch_semaphore_wait()
会做value--
操作;
dispatch_semaphore_signal()
会做value++
操作。主要在使用时,signal
和wait
是成对儿出现的。
总结
关于NSLock
,NSCondition
,NSConditionLock
都归属于Foundation
框架内部,由于OC的Foundation
没有开源,如果想进一步了解其内部实现,可以参考swift-Foundation
。