前言
- 这篇文章我们来聊聊锁,先来看看分析各种锁之前的性能的图表:
同步锁
1.@synchronized (self)
实现锁的优点就是我们不需要在代码中显式的创建锁对象,便可以实现锁的机制
@synchronized (self) {
//TODO:加锁操作
}
互斥锁
互斥锁是用来保证共享数据操作的完整性。每个对象都对应于一个可称为“互斥锁”的标记,这个标记用来保证在任一时刻,只能有一个线程访问对象
1.NSLock
NSLock遵循NSLocking协议
@protocol NSLocking
- (void)lock;//加锁
- (void)unlock;//解锁
@end
NSLock锁较为常用,通常是添加在一个线程中,要注意的是添加锁的线程不要是多次执行的,因为添加锁之后,其他线程要等待锁执行之后才能执行,所以添加锁的的代码最好是不耗时
- (BOOL)tryLock;//尝试加锁
//指定Date之前尝试加锁,如果在指定时间之前都不能加锁
- (BOOL)lockBeforeDate:(NSDate *)limit;
使用示例
NSLock *lock = [NSLock new];
//线程1
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSLog(@"线程1 尝试加速ing...");
[lock lock];
sleep(3);//睡眠5秒
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");
[lock unlock];
}else{
NSLog(@"失败");
}
});
2.pthread
pthread除了创建互斥锁,还可以创建递归锁、读写锁、once等锁
- (void)lock{
__block pthread_mutex_t mutex;
pthread_mutex_init(&mutex, NULL);
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSLog(@"+++++ 线程1 start");
pthread_mutex_lock(&mutex);
sleep(2);
pthread_mutex_unlock(&mutex);
NSLog(@"+++++ 线程1 end");
});
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSLog(@"----- 线程2 start");
pthread_mutex_lock(&mutex);
sleep(3);
pthread_mutex_unlock(&mutex);
NSLog(@"----- 线程2 end");
});
}
条件锁
1.NSCondition
NSCondition 的对象实际上是作为一个锁和线程检查器,锁主要是为了检测条件时保护数据源,执行条件引发的任务。线程检查器主要是根据条件决定是否继续运行线程,即线程是否被阻塞。
- (NSArray*)removeLastImage:(NSMutableArray *)images {
if (images.count > 0) {
NSCondition *condition = [[NSCondition alloc] init];
[condition lock];
[images removeLastObject];
[condition unlock];
return images.copy;
}else{
return NULL;
}
}
NSCondition可以给每个线程分别加锁,加锁后不影响其他线程进入临界区
- (void)testLock{
self.conditionArray = [NSMutableArray array];
self.condition = [[NSCondition alloc] init];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[self.condition lock];
if (self.conditionArray.count == 0) {
NSLog(@"等待制作数组");
[self.condition wait];
}
NSLog(@"获取对象进行操作:%@",self.conditionArray[0]);
[self.condition unlock];
});
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[self.condition lock];
id obj = @"xxxxxxx";
[self.conditionArray addObject:obj];
NSLog(@"创建了一个对象:%@",obj);
[self.condition signal];
[self.condition unlock];
});
}
- 等待2秒
NSCondition *cLock = [NSCondition new];
//线程1
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSLog(@"start");
[cLock lock];
[cLock waitUntilDate:[NSDate dateWithTimeIntervalSinceNow:2]];
NSLog(@"线程1");
[cLock unlock];
});
- 唤醒一个等待线程
NSCondition *cLock = [NSCondition new];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[cLock lock];
NSLog(@"线程1加锁成功");
[cLock wait];
NSLog(@"线程1");
[cLock unlock];
});
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[cLock lock];
NSLog(@"线程2加锁成功");
[cLock wait];
NSLog(@"线程2");
[cLock unlock];
});
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
sleep(2);
NSLog(@"唤醒一个等待的线程");
[cLock signal];
});
- 唤醒所有等待的线程
.........
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
sleep(2);
NSLog(@"唤醒所有等待的线程");
[cLock broadcast];
});
2.NSContionLock
只有 condition 参数与初始化时候的 condition 相等,才能正确进行加锁操作。而 unlockWithCondition: 并不是当 Condition 符合条件时才解锁,而是解锁之后,修改 Condition 的值。
- (void)testLock{
NSConditionLock *conditionLock = [[NSConditionLock alloc] init];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
for (int i = 0;i < 6;i++) {
[conditionLock lock];
NSLog(@"thread1:%d",i);
sleep(2);
[conditionLock unlockWithCondition:i];
}
});
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[conditionLock lockWhenCondition:2];
NSLog(@"thread2");
[conditionLock unlock];
});
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[conditionLock lockWhenCondition:3];
NSLog(@"thread3");
[conditionLock unlock];
});
}
递归锁
1.NSRecursiveLock
NSRecursiveLock 是递归锁,他和 NSLock 的区别在于,NSRecursiveLock 可以在一个线程中重复加锁(反正单线程内任务是按顺序执行的,不会出现资源竞争问题),NSRecursiveLock 会记录上锁和解锁的次数,当二者平衡的时候,才会释放锁,其它线程才可以上锁成功。
- (void)lock4 {
NSRecursiveLock *recursiveLock = [[NSRecursiveLock alloc] init];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
static void (^TestMethod)(int);
TestMethod = ^(int value) {
[recursiveLock lock];
if (value > 0) {
NSLog(@"加锁层数: %d",value);
TestMethod(--value);
}
NSLog(@"程序退出!");
[recursiveLock unlock];
};
TestMethod(3);
});
}
pthread
- (void)lock6 {
__block pthread_mutex_t recursiveMutex;
pthread_mutexattr_t recursiveMutexattr;
pthread_mutexattr_init(&recursiveMutexattr);
pthread_mutexattr_settype(&recursiveMutexattr, PTHREAD_MUTEX_RECURSIVE);
pthread_mutex_init(&recursiveMutex, &recursiveMutexattr);
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
static void (^TestMethod)(int);
TestMethod = ^(int value) {
pthread_mutex_lock(&recursiveMutex);
if (value > 0) {
NSLog(@"加锁层数: %d",value);
sleep(1);
TestMethod(--value);
}
NSLog(@"程序退出!");
pthread_mutex_unlock(&recursiveMutex);
};
TestMethod(3);
});
}
自旋锁
是用于多线程同步的一种锁,线程反复检查锁变量是否可用。由于线程在这一过程中保持执行,因此是一种忙等待。一旦获取了自旋锁,线程会一直保持该锁,直至显式释放自旋锁。 自旋锁避免了进程上下文的调度开销,因此对于线程只会阻塞很短时间的场合是有效的。
1.OSSpinLock
__block OSSpinLock oslock = OS_SPINLOCK_INIT;
//线程1
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSLog(@"线程1 准备上锁");
OSSpinLockLock(&oslock);
sleep(4);
NSLog(@"线程1");
OSSpinLockUnlock(&oslock);
NSLog(@"线程1 解锁成功");
});
//线程2
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSLog(@"线程2 准备上锁");
OSSpinLockLock(&oslock);
NSLog(@"线程2");
OSSpinLockUnlock(&oslock);
NSLog(@"线程2 解锁成功");
});
信号量
1.dispatch_semaphore
如果获取不到锁,会将当前线程阻塞、休眠,直到其他线程释放锁时,会唤醒当前线程。
dispatch_semaphore_t semaphore = dispatch_semaphore_create(1);
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
NSLog(@"task A");
dispatch_semaphore_signal(semaphore);
});
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
NSLog(@"task B");
dispatch_semaphore_signal(semaphore);
});
分布锁
1.NSDistributedLock
NSDistributedLock *lock = [[NSDistributedLock alloc] initWithPath:@"/Users/mac/Desktop/lock.lock"];
while (![lock tryLock]){
sleep(1);
}
//do something
[lock unlock];
死锁
死锁是由于多个线程(进程)在执行过程中,因为争夺资源而造成的互相等待现象,你可以理解为卡主了。产生死锁的必要条件有四个:
- 互斥条件:指进程对所分配到的资源进行排它性使用,即在一段时间内某资源只由一个进程占用。如果此时还有其它进程请求资源,则请求者只能等待,直至占有资源的进程用毕释放。
- 请求和保持条件:指进程已经保持至少一个资源,但又提出了新的资源请求,而该资源已被其它进程占有,此时请求进程阻塞,但又对自己已获得的其它资源保持不放。
- 不可剥夺条件:指进程已获得的资源,在未使用完之前,不能被剥夺,只能在使用完时由自己释放。
- 环路等待条件:指在发生死锁时,必然存在一个进程——资源的环形链,即进程集合{P0,P1,P2,···,Pn}中的P0正在等待一个P1占用的资源;P1正在等待P2占用的资源,……,Pn正在等待已被P0占用的资源。
NSLock *rLock = [NSLock new];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
static void (^TestBlock)(int);
TestBlock = ^(int value) {
[rLock lock];
if (value > 0) {
NSLog(@"线程%d", value);
TestBlock(value - 1);
}
[rLock unlock];
};
TestBlock(4);
});
最常见的就是 同步函数 + 主队列 的组合,本质是队列阻塞。
死锁是由于阻塞闭环造成的,那么我们只用消除其中一个因素,就能打破这个闭环,避免死锁。
参考文档
理解GCD死锁
iOS 中几种常用的锁总结
iOS 中常见的几种锁-代码示例
iOS进阶-细数iOS中的锁