锁的种类及其特点

在Objective-C中,如果有多个线程要执行同一份代码,就会出现线程安全问题。锁机制正是用于解决这一问题,确保同一时间内只有一个线程执行目标代码。




@synchronized(anObject)

说明:根据给定的对象,自动创建一个锁,并等待块中的代码执行完毕。当执行到代码结尾处时,锁就释放了,如

  1. // 该类用于多线程环境下的测试
  2. @implementation TestObj
  3. - (void)method1 {
  4. NSLog(@"Test: %@",NSStringFromSelector(_cmd));
  5. }
  6. - (void)method2 {
  7. NSLog(@"Test: %@",NSStringFromSelector(_cmd));
  8. }
  9. @end
  1. #import "TestObj.h"
  2. #import "ViewController.h"
  3. @implementation ViewController
  4. - (void)viewDidLoad {
  5. [super viewDidLoad];
  6. // 主线程中
  7. TestObj *obj = [[TestObj alloc] init];
  8. // 线程1
  9. dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
  10. @synchronized (self) {
  11. [obj method1];
  12. sleep(5);
  13. }
  14. });
  15. // 线程2
  16. dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
  17. sleep(1); // 以保证让线程2的代码后执行
  18. @synchronized (self) {
  19. [obj method2];
  20. }
  21. });
  22. }
  23. @end
  24. 结果:method1 执行后,等待5秒再执行 method2,即线程1锁住后,线程2会一直等待,直到线程1解锁后,method2 才会执行。

优点:不需要在代码中显式的创建锁对象,便可以实现锁的机制

缺点:@synchronized(anObject)方法针对anObject只有一个锁(多个同步块会共用这个锁),如果有多个同步块,则其他的同步块都要等待当前同步块执行完毕才能继续执行,降低了执行效率,如




NSLock

说明:最基本的锁对象,如

  1. #import "TestObj.h"
  2. #import "ViewController.h"
  3. @implementation ViewController
  4. - (void)viewDidLoad {
  5. [super viewDidLoad];
  6. // 主线程中
  7. TestObj *obj = [[TestObj alloc] init];
  8. NSLock *lock = [[NSLock alloc] init];
  9. // 线程1
  10. dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
  11. [lock lock];
  12. [obj method1];
  13. sleep(5);
  14. [lock unlock];
  15. });
  16. // 线程2
  17. dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
  18. sleep(1); // 以保证让线程2的代码后执行
  19. [lock lock];
  20. [obj method2];
  21. [lock unlock];
  22. });
  23. }
  24. @end

缺点:使用不当容易出现死锁,比如在递归或循环中使用(这种情况下,适合用NSRecursiveLock

  1. #import "ViewController1.h"
  2. #import "TestObj.h"
  3. @implementation ViewController1
  4. - (void)viewDidLoad {
  5. [super viewDidLoad];
  6. // 主线程中
  7. NSLock *theLock = [[NSLock alloc] init];
  8. TestObj *obj = [[TestObj alloc] init];
  9. // 线程1
  10. dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
  11. static void(^TestMethod)(int);
  12. TestMethod = ^(int value) {
  13. [theLock lock];
  14. if (value > 0) {
  15. [obj method1];
  16. sleep(5);
  17. TestMethod(value-1);
  18. }
  19. [theLock unlock];
  20. };
  21. TestMethod(5);
  22. });
  23. // 线程2
  24. dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
  25. sleep(1);
  26. [theLock lock];
  27. [obj method2];
  28. [theLock unlock];
  29. });
  30. }
  31. @end
  32. 结果:method1 只会被调用一次(预期是5次),method2 不会被调用。
  33. 原因:线程1被加锁2次(TestMethod [theLock lock] 被执行2次),却从未解锁(TestMethod [theLock unlock] 一次都没执行),出现了死锁,导致线程1与线程2都被锁住了。




C语言的pthread_mutex_t

  1. #import "pthread.h"
  2. #import "TestObj.h"
  3. #import "ViewController.h"
  4. @implementation ViewController
  5. - (void)viewDidLoad {
  6. [super viewDidLoad];
  7. // 主线程中
  8. TestObj *obj = [[TestObj alloc] init];
  9. __block pthread_mutex_t mutex;
  10. pthread_mutex_init(&mutex, NULL);
  11. // 线程1
  12. dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
  13. pthread_mutex_lock(&mutex);
  14. [obj method1];
  15. sleep(5);
  16. pthread_mutex_unlock(&mutex);
  17. });
  18. // 线程2
  19. dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
  20. sleep(1);
  21. pthread_mutex_lock(&mutex);
  22. [obj method2];
  23. pthread_mutex_unlock(&mutex);
  24. });
  25. }
  26. @end




GCD信号量

  1. #import "TestObj.h"
  2. #import "ViewController.h"
  3. @implementation ViewController
  4. - (void)viewDidLoad {
  5. [super viewDidLoad];
  6. // 主线程中
  7. TestObj *obj = [[TestObj alloc] init];
  8. dispatch_semaphore_t semaphore = dispatch_semaphore_create(1);
  9. // 线程1
  10. dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
  11. // 减少信号量
  12. dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
  13. [obj method1];
  14. sleep(5);
  15. // 增加信号量
  16. dispatch_semaphore_signal(semaphore);
  17. });
  18. // 线程2
  19. dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
  20. sleep(1);
  21. dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
  22. [obj method2];
  23. dispatch_semaphore_signal(semaphore);
  24. });
  25. }
  26. @end




NSRecursiveLock

说明:递归锁,该类定义的锁可以在同一线程多次lock,而不会造成死锁。递归锁会跟踪它被lock多少次,每次成功的lock都必须平衡调用unlock操作,只有所有的加锁和解锁操作都平衡的时候,锁才真正被释放给其他线程获得。

NSLock章节的死锁例子,只需将锁换成递归锁即可解决,如

  1. #import "ViewController1.h"
  2. #import "TestObj.h"
  3. @implementation ViewController1
  4. - (void)viewDidLoad {
  5. [super viewDidLoad];
  6. // 主线程中
  7. NSRecursiveLock *theLock = [[NSRecursiveLock alloc] init];
  8. TestObj *obj = [[TestObj alloc] init];
  9. // 线程1
  10. dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
  11. static void(^TestMethod)(int);
  12. TestMethod = ^(int value) {
  13. [theLock lock];
  14. if (value > 0) {
  15. [obj method1];
  16. sleep(5);
  17. TestMethod(value-1);
  18. }
  19. [theLock unlock];
  20. };
  21. TestMethod(5);
  22. });
  23. // 线程2
  24. dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
  25. sleep(1);
  26. [theLock lock];
  27. [obj method2];
  28. [theLock unlock];
  29. });
  30. }
  31. @end
  32. 结果:线程1 TestMethod 执行5次后,再执行线程2中的代码。




NSConditionLock

说明:条件锁,只有满足指定条件的情况下才能解锁或加锁,如

  1. #import "ViewController.h"
  2. @implementation ViewController1
  3. - (void)viewDidLoad {
  4. [super viewDidLoad];
  5. // 主线程中
  6. NSConditionLock *theLock = [[NSConditionLock alloc] init];
  7. // 线程1
  8. dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
  9. for (int i=0;i<=2;i++)
  10. {
  11. [theLock lock];
  12. NSLog(@"thread1:%d",i);
  13. sleep(1);
  14. // 满足指定条件时才解锁
  15. [theLock unlockWithCondition:i];
  16. }
  17. });
  18. // 线程2
  19. dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
  20. // 满足指定条件才加锁
  21. [theLock lockWhenCondition:2];
  22. NSLog(@"thread2");
  23. [theLock unlock];
  24. });
  25. }
  26. @end
  27. 结果:thread1:0thread1:1thread1:2thread2

注意:NSConditionLock也跟其它的锁一样:需要lockunlock对应,只是locklockWhenCondition:unlockunlockWithCondition:是可以随意组合的。




NSDistributedLock

说明:分布锁,通过文件系统实现,适用于在多个进程或多个程序之间构建互斥的场景(其他的锁都用于解决多线程之间的冲突),它并非继承于NSLock,只实现了tryLockunlockbreakLock,如(注:该示例没有亲自验证过)

  1. // 程序A
  2. dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
  3. NSDistributedLock *lock = [[NSDistributedLock alloc] initWithPath:@"file path"];
  4. [lock breakLock];
  5. [lock tryLock];
  6. sleep(5);
  7. [lock unlock];
  8. NSLog(@"appA: OK");
  9. });
  10. // 程序B
  11. dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
  12. NSDistributedLock *lock = [[NSDistributedLock alloc] initWithPath:@"file path"];
  13. while (![lock tryLock]) {
  14. NSLog(@"appB: waiting");
  15. sleep(1);
  16. }
  17. [lock unlock];
  18. NSLog(@"appB: OK");
  19. });
  20. 结果:当程序A运行的时候,程序B一直处于等待中,大概5秒之后,程序B打印出log

注意:程序A与程序B中的file path必须是同一个文件或文件夹的地址,如果该地址不存在,那么在tryLock返回YES时,会自动创建该文件或文件夹。在结束时该文件/文件夹会被清除,所以在选择的该路径的时候,应该选择一个不存在的路径,以防止误删了文件。