当前位置:主页 > 查看内容

iOS之深入解析保证线程安全的“锁”的使用和性能分析

发布时间:2021-06-14 00:00| 位朋友查看

简介:一、线程安全 在平时的开发中经常使用到多线程在使用多线程的过程中难免会遇到资源竞争的问题那么怎么来避免出现这种问题呢 当一个线程访问数据的时候其他的线程不能对其进行访问直到该线程访问完毕。简单来讲就是在同一时刻对同一个数据操作的线程只有一个……

一、线程安全

  • 在平时的开发中经常使用到多线程,在使用多线程的过程中,难免会遇到资源竞争的问题,那么怎么来避免出现这种问题呢?
  • 当一个线程访问数据的时候,其他的线程不能对其进行访问,直到该线程访问完毕。简单来讲就是在同一时刻,对同一个数据操作的线程只有一个。只有确保了这样,才能使数据不会被其他线程影响。而线程不安全,则是在同一时刻可以有多个线程对该数据进行访问,从而得不到预期的结果。
  • 比如写文件和读文件,当一个线程在写文件的时候,理论上来说,如果这个时候另一个线程来直接读取的话,那么得到的结果可能是无法预料的。
  • 通常使用锁的机制来保证线程安全,即确保同一时刻只有同一个线程来对同一个数据源进行访问。

二、锁的使用

① 互斥锁
  • 互斥锁是一种用于多线程编程中,防止两条线程同时对同一公共资源(例如全局变量)进行读写的机制,该目的是通过将代码切成一个个临界区而达成。
  • 互斥锁主要是@synchronized、NSLock、pthread_mutex。
  • @synchronized
    • @synchronized 是 iOS 中最常见的锁,用法很简单:
	 - (void)synchronized {
	    NSObject *dwobj = [NSObject new];  		
	    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
	        @synchronized(dwobj) {
	            NSLog(@"线程1开始");
	            sleep(3);
	            NSLog(@"线程1结束");
	        }
	    });
	    
	    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
	        sleep(1);
	        @synchronized(dwobj){
	            NSLog(@"线程2");
	        }
	    });
	}
    • 运行程序,结果如下:
	线程1开始
	线程1结束
	线程2
    • 可以看出,在线程 1 内容全部输出之后,才输出了线程 2 的内容,“线程1结束”与“线程2”都是在“线程1开始”之后输出的。
    • @synchronized(dwobj) 指令使用的 dwobj 为该锁的唯一标识,只有当标识相同时,才为满足互斥,如果线程 2 中的 @synchronized(dwobj) 改为 @synchronized(self) ,那么线程 2 就不会被阻塞。
    • @synchronized 指令实现锁的优点就是不需要在代码中显式的创建锁对象,便可以实现锁的机制,但作为一种预防措施;
    • @synchronized 块会隐式的添加一个异常处理例程来保护代码,该处理例程会在异常抛出的时候自动的释放互斥锁,所以如果不想让隐式的异常处理例程带来额外的开销,可以考虑使用锁对象。
    • @sychronized(dwobj){} 内部 dwobj 被释放或被设为 nil 不会影响锁的功能,但如果 dwobj 一开始就是 nil,那就会丢失了锁的功能。
  • NSLock
    • 先看看 iOS 中的 NSLock.h 文件,从代码中可以看出,这里定义了几个类:NSLock、NSConditionLock、NSRecursiveLock、NSCondition,然后有一个 NSLocking 协议:
	@protocol NSLocking
	- (void)lock;
	- (void)unlock;
	@end
    • 虽然 NSLock、NSConditionLock、NSRecursiveLock、NSCondition 都遵循的了 NSLocking 协议,但是它们并不相同。
    • NSLock 实现了最基本的互斥锁,遵循了 NSLocking 协议,通过 lock 和 unlock 来进行锁定和解锁。
	@interface NSLock : NSObject <NSLocking> {
	@private
	    void *_priv;
	}
	
	- (BOOL)tryLock;
	- (BOOL)lockBeforeDate:(NSDate *)limit;
	
	@property (nullable, copy) NSString *name API_AVAILABLE(macos(10.5), ios(2.0), watchos(2.0), tvos(9.0));
	
	@end
    • NSLock 的 lock 与 unlock 操作必须在同一线程,否则结果不确定甚至会引起死锁。使用如下:
	NSLock *dwlock = [NSLock new];
	dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        [dwlock lock];
        NSLog(@"线程1加锁成功");
        sleep(2);
        [dwlock unlock];
        NSLog(@"线程1解锁成功");
    });

    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        sleep(1);
        [dwlock lock];
        NSLog(@"线程2加锁成功");
        [dwlock unlock];
        NSLog(@"线程2解锁成功");
    });

	// 运行程序,结果如下
	线程1加锁成功
	线程1解锁成功
	线程2加锁成功
	线程2解锁成功
	NSLock *dwlock = [NSLock new];
	dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        [dwlock lock];
        NSLog(@"线程1加锁成功");
        sleep(2);
        [dwlock unlock];
        NSLog(@"线程1解锁成功");
    });
    
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        if ([dwlock tryLock]) {
            NSLog(@"线程3加锁成功");
            [dwlock unlock];
            NSLog(@"线程3解锁成功");
        } else {
            NSLog(@"线程3加锁失败");
        }
    });
    
    // 运行程序,结果如下
	线程1加锁成功
	线程3加锁失败
	线程1解锁成功
	NSLock *dwlock = [NSLock new];
	dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        [dwlock lock];
        NSLog(@"线程1加锁成功");
        sleep(2);
        [dwlock unlock];
        NSLog(@"线程1解锁成功");
    });
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        sleep(3);
        if ([dwlock tryLock]) {
            NSLog(@"线程4加锁成功");
            [dwlock unlock];
            NSLog(@"线程4解锁成功");
        } else {
            NSLog(@"线程4加锁失败");
        }
    });

	// 运行程序,结果如下
	线程1加锁成功
	线程1解锁成功
	线程4加锁成功
	线程4解锁成功
	NSLock *dwlock = [NSLock new];
	dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        [dwlock lock];
        NSLog(@"线程1加锁成功");
        sleep(2);
        [dwlock unlock];
        NSLog(@"线程1解锁成功");
    });

	dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        if ([dwlock lockBeforeDate:[NSDate dateWithTimeIntervalSinceNow:10]]) {
            NSLog(@"线程5加锁成功");
            [dwlock unlock];
            NSLog(@"线程5解锁成功");
        } else {
            NSLog(@"线程5加锁失败");
        }
    });

	// 运行程序,结果如下
	线程1加锁成功
	线程1解锁成功
	线程5加锁成功
	线程5解锁成功
    • 除 lock 和 unlock 方法外,NSLock 还提供了 tryLock 和 lockBeforeDate:两个方法。
    • 可以看到 tryLock 并不会阻塞线程,[dwlock tryLock] 能加锁返回 YES,不能加锁返回 NO,然后都会执行后续代码。
    • trylock 和 lock 使用场景:当前线程锁失败,也可以继续其它任务,用 trylock 合适;当前线程只有锁成功后,才会做一些有意义的工作,那就 lock,没必要 trylock。
    • lockBeforeDate: 方法会在所指定 Date 之前尝试加锁,会阻塞线程,如果在指定时间之前都不能加锁,则返回 NO,指定时间之前能加锁,则返回 YES。
    • 由于是互斥锁,当一个线程进行访问的时候,该线程获得锁,其他线程进行访问的时候,将被操作系统挂起,直到该线程释放锁,其他线程才能对其进行访问,从而却确保了线程安全。但是如果连续锁定两次,则会造成死锁问题。
    • 如果用 NSLock 的话,dwlock 先锁上了,但未执行解锁的时候,就会进入递归的下一层,而再次请求上锁,阻塞了该线程,线程被阻塞了,自然后面的解锁代码不会执行,而形成了死锁。而 NSRecursiveLock 递归锁就是为了解决这个问题。
  • pthread_mutex
    • pthread 表示 POSIX thread,定义了一组跨平台的线程相关的 API,POSIX 互斥锁是一种超级易用的互斥锁;
    • pthread_mutex 只需要使用 pthread_mutex_init 初始化一个 pthread_mutex_t;
    • pthread_mutex_lock 或者 pthread_mutex_trylock 来锁定 ;
    • pthread_mutex_unlock 来解锁;
    • 当使用完成后,记得调用 pthread_mutex_destroy 来销毁锁。
    • pthread_mutex 常用 API:
	pthread_mutex_init(pthread_mutex_t *restrict _Nonnull, const pthread_mutexattr_t *restrict _Nullable);
	pthread_mutex_lock(pthread_mutex_t * _Nonnull);
	pthread_mutex_trylock(pthread_mutex_t * _Nonnull);
	pthread_mutex_unlock(pthread_mutex_t * _Nonnull);
	pthread_mutex_destroy(pthread_mutex_t * _Nonnull);
    • pthread_mutex 的使用:
	__block pthread_mutex_t dwlock;
    pthread_mutex_init(&dwlock, NULL);
	
	dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        pthread_mutex_lock(&dwlock);
        NSLog(@"线程1开始");
        sleep(3);
        NSLog(@"线程1结束");
        pthread_mutex_unlock(&dwlock);
    });
	
	dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        sleep(1);
        pthread_mutex_lock(&dwlock);
        NSLog(@"线程2");
        pthread_mutex_unlock(&dwlock);
    });

	// 运行程序,结果如下:
	线程1开始
	线程1结束
	线程2
② 自旋锁
  • 在自旋锁中,线程会反复检查变量是否可用,由于线程在这个过程中一致保持执行,所以是一种忙等待, 一旦获取了自旋锁,线程就会一直保持该锁,直到显式释放自旋锁。
  • 自旋锁避免了进程上下文的调度开销,因此对于线程只会阻塞很短时间的场合是有效的,对于 iOS 属性的修饰符 atomic,它自带一把自旋锁。
  • 自旋锁主要有 OSSpinLock 和 atomic。
  • OSSpinLock 是一种自旋锁,和互斥锁类似,都是为了保证线程安全的锁。
  • 但二者的区别是不一样的,对于互斥锁,当一个线程获得这个锁之后,其他想要获得此锁的线程将会被阻塞,直到该锁被释放。但自选锁不一样,当一个线程获得锁之后,其他线程将会一直循环在哪里查看是否该锁被释放。
  • 自旋锁比较适用于锁的持有者保存时间较短的情况下。
  • 自旋锁的加锁,解锁,尝试加锁:
	typedef int32_t OSSpinLock;
	
	// 加锁
	void    OSSpinLockLock( volatile OSSpinLock *__lock );
	// 尝试加锁
	bool    OSSpinLockTry( volatile OSSpinLock *__lock );
	// 解锁
	void    OSSpinLockUnlock( volatile OSSpinLock *__lock );
  • 自旋锁的使用如下:
	#import <libkern/OSAtomic.h>
	
	__block OSSpinLock theLock = OS_SPINLOCK_INIT;
	dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        OSSpinLockLock(&theLock);
        NSLog(@"线程1开始");
        sleep(3);
        NSLog(@"线程1结束");
        OSSpinLockUnlock(&theLock);    
    });

    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        OSSpinLockLock(&theLock);
        sleep(1);
        NSLog(@"线程2");
        OSSpinLockUnlock(&theLock); 
    });

	//运行程序,结果如下:
	线程1开始
	线程1结束
	线程2
  • OSSpinLock 在iOS 10.0中被 <os/lock.h> 中的 os_unfair_lock 取代。
  • os_unfair_lock 解决了优先级反转问题。
	// 初始化
	os_unfair_lock_t unfairLock = &(OS_UNFAIR_LOCK_INIT);
	// 加锁
	os_unfair_lock_lock(unfairLock);
	// 尝试加锁
	BOOL b = os_unfair_lock_trylock(unfairLock);
	// 解锁
	os_unfair_lock_unlock(unfairLock);
③ 条件锁
  • 条件锁就是条件变量,当进程的某些资源要求不满足时就进入休眠,即锁住了,当资源被分配到了,条件锁打开了,进程继续运行。
  • 条件锁主要是 NSConditionLock 和 NSConditio。
  • NSConditionLock 对象所定义的互斥锁可以在使得在某个条件下进行锁定和解锁,它和 NSLock 类似,都遵循 NSLocking 协议,方法都类似,只是多了一个 condition 属性,以及每个操作都多了一个关于 condition 属性的方法,例如 tryLock、tryLockWhenCondition:,所以 NSConditionLock 可以称为条件锁。
  • 只有 condition 参数与初始化时候的 condition 相等,lock 才能正确进行加锁操作。
  • unlockWithCondition: 并不是当 condition 符合条件时才解锁,而是解锁之后,修改 condition 的值。
	@interface NSConditionLock : NSObject <NSLocking> {
	@private
	    void *_priv;
	}
	
	- (instancetype)initWithCondition:(NSInteger)condition NS_DESIGNATED_INITIALIZER;
	
	@property (readonly) NSInteger condition;
	- (void)lockWhenCondition:(NSInteger)condition;
	- (BOOL)tryLock;
	- (BOOL)tryLockWhenCondition:(NSInteger)condition;
	- (void)unlockWithCondition:(NSInteger)condition;
	- (BOOL)lockBeforeDate:(NSDate *)limit;
	- (BOOL)lockWhenCondition:(NSInteger)condition beforeDate:(NSDate *)limit;
	
	@property (nullable, copy) NSString *name API_AVAILABLE(macos(10.5), ios(2.0), watchos(2.0), tvos(9.0));
	
	@end
  • NSConditionLock 使用如下:
	NSConditionLock *dwlock = [[NSConditionLock alloc] initWithCondition:0];
	dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        [dwlock lock];
        NSLog(@"线程1加锁成功");
        sleep(1);
        [dwlock unlock];
        NSLog(@"线程1解锁成功");
    });

    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        sleep(1);
        [dwlock lockWhenCondition:1];
        NSLog(@"线程2加锁成功");
        [dwlock unlock];
        NSLog(@"线程2解锁成功");
    });

    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        sleep(2);
        if ([dwlock tryLockWhenCondition:0]) {
            NSLog(@"线程3加锁成功");
            sleep(2);
            [dwlock unlockWithCondition:2];
            NSLog(@"线程3解锁成功");
        } else {
            NSLog(@"线程3尝试加锁失败");
        }
    });

    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        if ([dwlock lockWhenCondition:2 beforeDate:[NSDate dateWithTimeIntervalSinceNow:10]]) {
            NSLog(@"线程4加锁成功");
            [dwlock unlockWithCondition:1];
            NSLog(@"线程4解锁成功");
        } else {
            NSLog(@"线程4尝试加锁失败");
        }
    });

	// 运行程序,结果如下
	线程1加锁成功
	线程1解锁成功	
	线程3加锁成功
	线程3解锁成功
	线程4加锁成功
	线程4解锁成功
	线程2加锁成功
	线程2解锁成功
  • NSCondition 是一种特殊类型的锁,通过它可以实现不同线程的调度。一个线程被某一个条件所阻塞,直到另一个线程满足该条件从而发送信号给该线程使得该线程可以正确的执行。比如说,你可以开启一个线程下载图片,一个线程处理图片,这样的话,需要处理图片的线程由于没有图片会阻塞,当下载线程下载完成之后,则满足了需要处理图片的线程的需求,这样可以给定一个信号,让处理图片的线程恢复运行。
  • NSCondition 的对象实际上作为一个锁和一个线程检查器,锁上之后其它线程也能上锁,而之后可以根据条件决定是否继续运行线程,即线程是否要进入 waiting 状态,如果进入 waiting 状态,当其它线程中的该锁执行 signal 或者 broadcast 方法时,线程被唤醒,继续运行之后的方法。
  • NSCondition 可以手动控制线程的挂起与唤醒,可以利用这个特性设置依赖。
	@interface NSCondition : NSObject <NSLocking> {
	@private
	    void *_priv;
	}
	
	- (void)wait;      // 挂起线程
	- (BOOL)waitUntilDate:(NSDate *)limit; // 什么时候挂起线程
	- (void)signal;    // 唤醒一条挂起线程
	- (void)broadcast; // 唤醒所有挂起线程
	
	@property (nullable, copy) NSString *name API_AVAILABLE(macos(10.5), ios(2.0), watchos(2.0), tvos(9.0));
	
	@end
  • NSCondition 使用如下:
	NSCondition *dwCondition = [NSCondition new];
    
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        [dwCondition lock];
        NSLog(@"线程1线程加锁");
        [dwCondition wait];
        NSLog(@"线程1线程唤醒");
        [dwCondition unlock];
        NSLog(@"线程1线程解锁");
    });
    
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        [dwCondition lock];
        NSLog(@"线程2线程加锁");
        if ([dwCondition waitUntilDate:[NSDate dateWithTimeIntervalSinceNow:10]]) {
            NSLog(@"线程2线程唤醒");
            [dwCondition unlock];
            NSLog(@"线程2线程解锁");
        }
    });
    
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        sleep(2);
        [dwCondition signal];
    });

	// 运行程序,结果如下:
	线程1线程加锁
	线程2线程加锁
	线程1线程唤醒
	线程1线程解锁
	// 如果 [dwCondition signal]; 改成 [dwCondition broadcast];
	dispatch_async(dispatch_get_global_queue(0, 0), ^{
        sleep(2);
        [dwCondition broadcast];
    });

	// 运行程序,结果如下:
	线程1线程加锁
	线程2线程加锁
	线程1线程唤醒
	线程1线程解锁
	线程2线程唤醒
	线程2线程解锁
  • NSCondition 在加上锁之后,调用条件对象的 wait 或 waitUntilDate: 方法来阻塞线程,直到条件对象发出唤醒信号或者超时之后,再进行之后的操作。
  • NSCondition 的 signal 和 broadcast 方法的区别在于,signal 只是一个信号量,只能唤醒一个等待的线程,想唤醒多个就得多次调用,而 broadcast 可以唤醒所有在等待的线程。
④ 递归锁
  • 递归锁就是同一个线程可以加锁 N 次而不会引发死锁。递归锁是特殊的互斥锁,即是带有递归性质的互斥锁。
  • 递归锁主要为 pthread_mutex(recursive) 和 NSRecursiveLock。
  • NSRecursiveLock 是递归锁,顾名思义,可以被一个线程多次获得,而不会引起死锁。
    • NSRecursiveLock 记录了成功获得锁的次数,每一次成功的获得锁,必须有一个配套的释放锁和其对应,这样才不会引起死锁。
    • NSRecursiveLock 会记录上锁和解锁的次数,当二者平衡的时候,才会释放锁,其它线程才可以上锁成功。
	@interface NSRecursiveLock : NSObject <NSLocking> {
	@private
	    void *_priv;
	}
	
	- (BOOL)tryLock;
	- (BOOL)lockBeforeDate:(NSDate *)limit;
	
	@property (nullable, copy) NSString *name API_AVAILABLE(macos(10.5), ios(2.0), watchos(2.0), tvos(9.0));
	
	@end
  • NSRecursiveLock 使用如下:
	NSRecursiveLock *dwlock = [[NSRecursiveLock alloc] init];
	dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        static void (^RecursiveBlock)(int);
        RecursiveBlock = ^(int value) {
            [dwlock lock];
            NSLog(@"%d加锁成功",value);
            if (value > 0) {
                NSLog(@"value:%d", value);
                RecursiveBlock(value - 1);
            }
            [dwlock unlock];
            NSLog(@"%d解锁成功",value);
        };
        RecursiveBlock(3);
    });

	// 运行程序,结果如下
	3加锁成功
	value:3
	2加锁成功
	value:2
	1加锁成功
	value:1
	0加锁成功
	0解锁成功
	1解锁成功
	2解锁成功
	3解锁成功
  • pthread_mutex(recursive) 的使用如下:
	__block pthread_mutex_t dwlock;
    
    pthread_mutexattr_t attr;
    pthread_mutexattr_init(&attr);
    pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE);
    pthread_mutex_init(&cjlock, &attr);
    pthread_mutexattr_destroy(&attr);
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        static void (^RecursiveBlock)(int);
        RecursiveBlock = ^(int value) {
            pthread_mutex_lock(&dwlock);
            NSLog(@"%d加锁成功",value);
            if (value > 0) {
                NSLog(@"value = %d", value);
                sleep(1);
                RecursiveBlock(value - 1);
            }
            NSLog(@"%d解锁成功",value);
            pthread_mutex_unlock(&dwlock);
        };
        RecursiveBlock(3);
    });

	// 运行程序,结果如下:
	3加锁成功
	value = 3
	2加锁成功
	value = 2
	1加锁成功
	value = 1
	0加锁成功
	0解锁成功
	1解锁成功
	2解锁成功
	3解锁成功
  • pthread_mutex(recursive) 的用法和 NSLock 的 lock unlock 用法一致,而它也有一个 pthread_mutex_trylock 方法,pthread_mutex_trylock 和 tryLock 的区别在于,tryLock 返回的是 YES 和 NO,pthread_mutex_trylock 加锁成功返回的是 0,失败返回的是错误提示码。
  • pthread_mutex(recursive) 作用和 NSRecursiveLock 递归锁类似。如果使用 pthread_mutex_init(&theLock, NULL); 初始化锁的话,上面的代码的第二部分会出现死锁现象,使用递归锁就可以避免这种现象。
⑤ 信号量
  • 信号量是一种更高级的同步机制,互斥锁可以说是 semaphore 在仅取值0/1时的特例,信号量可以有更多的取值空间,用来实现更加复杂的同步,而不单单是线程间互斥。
  • dispatch_semaphore 使用信号量机制实现锁,等待信号和发送信号。
  • dispatch_semaphore 是 GCD 用来同步的一种方式,与它相关的只有三个函数,一个是创建信号量,一个是等待信号,一个是发送信号。
  • dispatch_semaphore 的机制就是当有多个线程进行访问的时候,只要有一个获得了信号,其他线程的就必须等待该信号释放。
  • dispatch_semaphore 的常用相关API:
	dispatch_semaphore_create(long value);
	dispatch_semaphore_wait(dispatch_semaphore_t  _Nonnull dsema, dispatch_time_t timeout);
	dispatch_semaphore_signal(dispatch_semaphore_t  _Nonnull dsema);
  • dispatch_semaphore 的使用如下:
	dispatch_semaphore_t semaphore = dispatch_semaphore_create(1);
    dispatch_time_t overTime = dispatch_time(DISPATCH_TIME_NOW, 6 * NSEC_PER_SEC);	
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        dispatch_semaphore_wait(semaphore, overTime);
        NSLog(@"线程1开始");
        sleep(5);
        NSLog(@"线程1结束");
        dispatch_semaphore_signal(semaphore);
    });
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        sleep(1);
        dispatch_semaphore_wait(semaphore, overTime);
        NSLog(@"线程2开始");
        dispatch_semaphore_signal(semaphore);
    });

	// 执行结果如下:
	线程1开始
	线程1结束
	线程2开始
	// 如果将上面的 overTime 改成 3 秒,执行结果如下
	线程1开始
	线程2开始
	线程1结束
  • dispatch_semaphore 和 NSCondition 类似,都是一种基于信号的同步方式,但 NSCondition 信号只能发送,不能保存(如果没有线程在等待,则发送的信号会失效),而 dispatch_semaphore 能保存发送的信号,dispatch_semaphore 的核心是 dispatch_semaphore_t 类型的信号量。
  • dispatch_semaphore_create(1) 方法可以创建一个 dispatch_semaphore_t 类型的信号量,设定信号量的初始值为 1。注意,这里的传入的参数必须大于或等于 0,否则 dispatch_semaphore_create 会返回 NULL。
  • dispatch_semaphore_wait(semaphore, overTime); 方法会判断 semaphore 的信号值是否大于 0。大于 0 不会阻塞线程,消耗掉一个信号,执行后续任务。如果信号值为 0,该线程会和 NSCondition 一样直接进入 waiting 状态,等待其他线程发送信号唤醒线程去执行后续任务,或者当 overTime 时限到了,也会执行后续任务。
  • dispatch_semaphore_signal(semaphore); 发送信号,如果没有等待的线程接受信号,则使 signal 信号值加一(做到对信号的保存)。
  • 一个 dispatch_semaphore_wait(semaphore, overTime); 方法会去对应一个 dispatch_semaphore_signal(semaphore); 看起来像 NSLock 的 lock 和 unlock,其实可以这样理解,区别只在于有信号量这个参数,lock unlock 只能同一时间,一个线程访问被保护的临界区,而如果 dispatch_semaphore 的信号量初始值为 x ,则可以有 x 个线程同时访问被保护的临界区。
⑥ 读写锁
  • 读写锁实际是一种特殊的自旋锁。将对共享资源的访问分成读者和写者,读者只对共享资源进行读访问,写者则需要对共享资源进行写操作,这种锁相对于自旋锁而言,能提高并发性。
  • 一个读写锁同时只能有一个写者或者多个读者,但不能既有读者又有写者,在读写锁保持期间也是抢占失效的。
  • 如果读写锁当前没有读者,也没有写者,那么写者可以立刻获得读写锁,否则它必须自旋在那里, 直到没有任何写者或读者;如果读写锁没有写者,那么读者可以立。
  • 其实基本的锁就包括三类:自旋锁、互斥锁、读写锁,其他的比如条件锁、递归锁、信号量都是上层的封装和实现。

三、锁的性能

  • 借用不再安全的 OSSpinLock中的对锁的性能测试,只是测试了单线程的情况,不能反映多线程下的实际性能,还有这里比较的只是加锁立马解锁的时间消耗,并没有计算竞争时候的时间消耗。结果如下:

在这里插入图片描述

  • 可以看到除了 OSSpinLock 外,dispatch_semaphore 和 pthread_mutex 性能是最高的。有消息称,苹果在新系统中已经优化了 pthread_mutex 的性能,所以它看上去和 OSSpinLock 差距并没有那么大了。
  • 其实每一种锁基本上都是加锁、等待、解锁的步骤,理解了这三个步骤就可以快速学会各种锁的用法。
  • @synchronized 的效率最低,不过它的确用起来最方便,所以如果没什么性能瓶颈的话,可以选择使用 @synchronized。
  • 当性能要求较高时候,可以使用 pthread_mutex 或者 dispath_semaphore,由于 OSSpinLock 不能很好的保证线程安全,而在只有在 iOS10 中才有 os_unfair_lock ,所以,前两个是比较好的选择,既可以保证速度,又可以保证线程安全。
  • 对于 NSLock 及其子类,速度来说 NSLock < NSCondition < NSRecursiveLock < NSConditionLock 。
;原文链接:https://blog.csdn.net/Forever_wj/article/details/115581992
本站部分内容转载于网络,版权归原作者所有,转载之目的在于传播更多优秀技术内容,如有侵权请联系QQ/微信:153890879删除,谢谢!

推荐图文

  • 周排行
  • 月排行
  • 总排行

随机推荐