锁的种类及其特点
在Objective-C中,如果有多个线程要执行同一份代码,就会出现线程安全问题。锁机制正是用于解决这一问题,确保同一时间内只有一个线程执行目标代码。
@synchronized(anObject)
说明:根据给定的对象,自动创建一个锁,并等待块中的代码执行完毕。当执行到代码结尾处时,锁就释放了,如
// 该类用于多线程环境下的测试@implementation TestObj- (void)method1 {NSLog(@"Test: %@",NSStringFromSelector(_cmd));}- (void)method2 {NSLog(@"Test: %@",NSStringFromSelector(_cmd));}@end
#import "TestObj.h"#import "ViewController.h"@implementation ViewController- (void)viewDidLoad {[super viewDidLoad];// 主线程中TestObj *obj = [[TestObj alloc] init];// 线程1dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{@synchronized (self) {[obj method1];sleep(5);}});// 线程2dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{sleep(1); // 以保证让线程2的代码后执行@synchronized (self) {[obj method2];}});}@end结果:method1 执行后,等待5秒再执行 method2,即线程1锁住后,线程2会一直等待,直到线程1解锁后,method2 才会执行。
优点:不需要在代码中显式的创建锁对象,便可以实现锁的机制
缺点:@synchronized(anObject)方法针对anObject只有一个锁(多个同步块会共用这个锁),如果有多个同步块,则其他的同步块都要等待当前同步块执行完毕才能继续执行,降低了执行效率,如
NSLock
说明:最基本的锁对象,如
#import "TestObj.h"#import "ViewController.h"@implementation ViewController- (void)viewDidLoad {[super viewDidLoad];// 主线程中TestObj *obj = [[TestObj alloc] init];NSLock *lock = [[NSLock alloc] init];// 线程1dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{[lock lock];[obj method1];sleep(5);[lock unlock];});// 线程2dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{sleep(1); // 以保证让线程2的代码后执行[lock lock];[obj method2];[lock unlock];});}@end
缺点:使用不当容易出现死锁,比如在递归或循环中使用(这种情况下,适合用NSRecursiveLock)
#import "ViewController1.h"#import "TestObj.h"@implementation ViewController1- (void)viewDidLoad {[super viewDidLoad];// 主线程中NSLock *theLock = [[NSLock alloc] init];TestObj *obj = [[TestObj alloc] init];// 线程1dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{static void(^TestMethod)(int);TestMethod = ^(int value) {[theLock lock];if (value > 0) {[obj method1];sleep(5);TestMethod(value-1);}[theLock unlock];};TestMethod(5);});// 线程2dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{sleep(1);[theLock lock];[obj method2];[theLock unlock];});}@end结果:method1 只会被调用一次(预期是5次),method2 不会被调用。原因:线程1被加锁2次(TestMethod内 [theLock lock] 被执行2次),却从未解锁(TestMethod内 [theLock unlock] 一次都没执行),出现了死锁,导致线程1与线程2都被锁住了。
C语言的pthread_mutex_t
#import "pthread.h"#import "TestObj.h"#import "ViewController.h"@implementation ViewController- (void)viewDidLoad {[super viewDidLoad];// 主线程中TestObj *obj = [[TestObj alloc] init];__block pthread_mutex_t mutex;pthread_mutex_init(&mutex, NULL);// 线程1dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{pthread_mutex_lock(&mutex);[obj method1];sleep(5);pthread_mutex_unlock(&mutex);});// 线程2dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{sleep(1);pthread_mutex_lock(&mutex);[obj method2];pthread_mutex_unlock(&mutex);});}@end
GCD信号量
#import "TestObj.h"#import "ViewController.h"@implementation ViewController- (void)viewDidLoad {[super viewDidLoad];// 主线程中TestObj *obj = [[TestObj alloc] init];dispatch_semaphore_t semaphore = dispatch_semaphore_create(1);// 线程1dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{// 减少信号量dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);[obj method1];sleep(5);// 增加信号量dispatch_semaphore_signal(semaphore);});// 线程2dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{sleep(1);dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);[obj method2];dispatch_semaphore_signal(semaphore);});}@end
NSRecursiveLock
说明:递归锁,该类定义的锁可以在同一线程多次lock,而不会造成死锁。递归锁会跟踪它被lock多少次,每次成功的lock都必须平衡调用unlock操作,只有所有的加锁和解锁操作都平衡的时候,锁才真正被释放给其他线程获得。
NSLock章节的死锁例子,只需将锁换成递归锁即可解决,如
#import "ViewController1.h"#import "TestObj.h"@implementation ViewController1- (void)viewDidLoad {[super viewDidLoad];// 主线程中NSRecursiveLock *theLock = [[NSRecursiveLock alloc] init];TestObj *obj = [[TestObj alloc] init];// 线程1dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{static void(^TestMethod)(int);TestMethod = ^(int value) {[theLock lock];if (value > 0) {[obj method1];sleep(5);TestMethod(value-1);}[theLock unlock];};TestMethod(5);});// 线程2dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{sleep(1);[theLock lock];[obj method2];[theLock unlock];});}@end结果:线程1中 TestMethod 执行5次后,再执行线程2中的代码。
NSConditionLock
说明:条件锁,只有满足指定条件的情况下才能解锁或加锁,如
#import "ViewController.h"@implementation ViewController1- (void)viewDidLoad {[super viewDidLoad];// 主线程中NSConditionLock *theLock = [[NSConditionLock alloc] init];// 线程1dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{for (int i=0;i<=2;i++){[theLock lock];NSLog(@"thread1:%d",i);sleep(1);// 满足指定条件时才解锁[theLock unlockWithCondition:i];}});// 线程2dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{// 满足指定条件才加锁[theLock lockWhenCondition:2];NSLog(@"thread2");[theLock unlock];});}@end结果:thread1:0,thread1:1,thread1:2,thread2
注意:NSConditionLock也跟其它的锁一样:需要lock与unlock对应,只是lock、lockWhenCondition:与unlock、unlockWithCondition:是可以随意组合的。
NSDistributedLock
说明:分布锁,通过文件系统实现,适用于在多个进程或多个程序之间构建互斥的场景(其他的锁都用于解决多线程之间的冲突),它并非继承于NSLock,只实现了tryLock,unlock,breakLock,如(注:该示例没有亲自验证过)
// 程序Adispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{NSDistributedLock *lock = [[NSDistributedLock alloc] initWithPath:@"file path"];[lock breakLock];[lock tryLock];sleep(5);[lock unlock];NSLog(@"appA: OK");});// 程序Bdispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{NSDistributedLock *lock = [[NSDistributedLock alloc] initWithPath:@"file path"];while (![lock tryLock]) {NSLog(@"appB: waiting");sleep(1);}[lock unlock];NSLog(@"appB: OK");});结果:当程序A运行的时候,程序B一直处于等待中,大概5秒之后,程序B打印出log。
注意:程序A与程序B中的file path必须是同一个文件或文件夹的地址,如果该地址不存在,那么在tryLock返回YES时,会自动创建该文件或文件夹。在结束时该文件/文件夹会被清除,所以在选择的该路径的时候,应该选择一个不存在的路径,以防止误删了文件。
