前言
这篇文章我们来聊聊锁,先来看看分析各种锁之前的性能的图表:
同步锁
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); });
最常见的就是 同步函数 + 主队列 的组合,本质是队列阻塞。
死锁是由于阻塞闭环造成的,那么我们只用消除其中一个因素,就能打破这个闭环,避免死锁。