锁的类别
- NSLocking
- NSLock
- NSConditionLock 条件锁
- NSRecursiveLock 递归锁
- NSCondition
- NSDistributedLock 分布锁
- @sychronized
- dispatch_semaphore
- OSSpinLock 自旋锁
- pthread_mutex
首先我们要实现一种简单的线程访问导致的数据混乱的情况
- (void)threadTestLearn
{
__block NSInteger flag = 0;
void (^positive)() = ^{
while (1) {
[NSThread sleepForTimeInterval:1];
flag = 1;
[NSThread sleepForTimeInterval:0.5];
NSLog(@"1 == %ld", flag);
}
};
void (^negative)() = ^{
while (1) {
[NSThread sleepForTimeInterval:1];
flag = -1;
[NSThread sleepForTimeInterval:0.5];
NSLog(@"-1 == %ld", flag);
}
};
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), positive);
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), negative);
}
这个方法的打印结果,预期应该是
1 == 1
-1 == -1
1 == 1
而事实上,由于没有互斥访问机制,打印结果出现了
1 == -1
-1 == 1
NSLocking
诸如 NSLock、NSConditionLock 等锁都继承了 NSLocking 协议,NSLocking 协议简单粗暴地定义了两个方法
@protocol NSLocking
- (void)lock;
- (void)unlock;
@end
一个是加锁,一个是解锁。
NSLock
同一个 NSLock 对象显式调用 lock 函数后,除非自己显示调用 unlock 函数,否则其他线程均不能继续对此 NSLock 对象加锁,从而达到互斥访问的目的。 所以使用也很简单
- (void)threadTestLearn
{
__block NSInteger flag = 0;
NSLock *lock = [NSLock new];
void (^positive)() = ^{
while (1) {
[lock lock];
[NSThread sleepForTimeInterval:1];
flag = 1;
[NSThread sleepForTimeInterval:0.5];
NSLog(@"1 == %ld", flag);
[lock unlock];
}
};
void (^negative)() = ^{
while (1) {
[lock lock];
[NSThread sleepForTimeInterval:1];
flag = -1;
[NSThread sleepForTimeInterval:0.5];
NSLog(@"-1 == %ld", flag);
[lock unlock];
}
};
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), positive);
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), negative);
}
如果希望获取锁时不阻塞线程,则考虑使用 tryLock 方法,它会在获取锁失败后返回 NO,可以继续执行后面的代码。
lockBeforeDate 方法在指定的时间以前得到锁。YES: 在指定时间之前获得了锁;NO: 在指定时间之前没有获得锁。该线程将被阻塞,直到获得了锁,或者指定时间过期。
NSLockConditionLock
NSConditionLock 是一种条件锁,除了基本的lock与unlock函数,还提供了 lockWithCondition 以及 unlockWithCondition 方法,适合多个线程的工作需要按照顺序执行的情景。其中 unLockWithCondition 会把 condition 变量修改为指定参数后解锁。
- (void)threadTestLearn
{
__block NSInteger flag = 0;
__block NSInteger mutext = 1;
NSConditionLock *lock = [[NSConditionLock alloc] initWithCondition:mutext];
void (^positive)() = ^{
while (1) {
[lock lockWhenCondition:1];
[NSThread sleepForTimeInterval:1];
flag = 1;
[NSThread sleepForTimeInterval:0.5];
NSLog(@"1 == %ld", flag);
[lock unlockWithCondition:-1];
}
};
void (^negative)() = ^{
while (1) {
[lock lockWhenCondition:-1];
[NSThread sleepForTimeInterval:1];
flag = -1;
[NSThread sleepForTimeInterval:0.5];
NSLog(@"-1 == %ld", flag);
[lock unlockWithCondition:1];
}
};
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), positive);
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), negative);
}
这里通过 mutex 变量严格保证了 positive 和 negative 方法会依次调用,并且第一个调用的方法一定是 positive 方法,同时如果将 negative 中的语句改成
[lock unlockWithCondition:0];
则两个方法都只会被调用一次就停止了,因为都获得不了正确的 condition 所以无法获得锁。
NSRecursiveLock
NSRecursiveLock 是递归锁,用于解决递归调用中的死锁问题。当一个方法加锁以后递归调用自己,会再次进行加锁,由于锁没有被释放所以线程会被阻塞掉,线程阻塞后将永远不能解锁,因此发生死锁。
NSRecursiveLock 实际上定义的是一个递归锁,主要是用在循环或递归操作中。它可以允许同一线程多次加锁,而不会造成死锁。递归锁会跟踪它被 lock 的次数。每次成功的 lock 都必须平衡调用 unlock 操作。只有所有达到这种平衡,锁最后才能被释放,以供其它线程使用。
NSRecursiveLock *recursiveLock = [NSRecursiveLock new];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
static void (^test)(int);
test = ^(int value)
{
[recursiveLock lock];
if (value > 0)
{
[NSThread sleepForTimeInterval:0.5];
NSLog(@"%d", value);
test(--value);
}
else
{
[recursiveLock unlock];
return;
}
NSLog(@"%d", value);
[recursiveLock unlock];
};
__block int value = 5;
test(value);
});
NSCondition
NSCondition 与 NSConditionLock 是两回事,它们之间最大的区别在于 NSCondition 支持在获得锁之后阻塞线程并放弃锁,然后等待被唤醒后可以重新获得锁。
这里被唤醒有两种方式,signal 和 broadcast,前者只会唤醒一个 wait 的线程,而后者会唤醒所有 wait 的线程。
但是有一个问题:在多个线程 wait 的场景下,一个线程调用了 broadcast 后如何唤醒多个 wait 的线程,能否保证线程安全,以及这个线程在未 unlock 前执行操作如何保证线程安全。
- (void)threadTestLearn
{
__block NSInteger flag = 0;
__block NSInteger mutex = 1;
NSCondition *condition = [NSCondition new];
void (^positive)() = ^{
while (1) {
[condition lock];
NSLog(@"thread1");
while (mutex == 0)
{
[condition wait];
}
[NSThread sleepForTimeInterval:1];
flag = 1;
[NSThread sleepForTimeInterval:0.5];
NSLog(@"1 == %ld", flag);
mutex = 0;
[condition signal];
[condition unlock];
}
};
void (^negative)() = ^{
while (1) {
[condition lock];
NSLog(@"thread-1");
if (mutex == 0)
{
[condition wait];
}
[NSThread sleepForTimeInterval:1];
flag = -1;
[NSThread sleepForTimeInterval:0.5];
NSLog(@"-1 == %ld", flag);
mutex = 0;
[condition signal];
[condition unlock];
}
};
void (^zero)() = ^{
while (1) {
[condition lock];
if (mutex != 0)
{
[condition wait];
}
NSLog(@"mutex 0");
[NSThread sleepForTimeInterval:1];
mutex = 1;
[condition broadcast];
[condition unlock];
}
};
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), positive);
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), negative);
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), zero);
}
@synchronized
@synchronized 可以保证唯一标识下的资源互斥访问,不需要显式创建锁对象,使用方便,但是表现性能不佳。
__block NSInteger flag = 0;
void (^positive)() = ^{
while (1) {
@synchronized (@(flag)) {
[NSThread sleepForTimeInterval:1];
flag = 1;
[NSThread sleepForTimeInterval:0.5];
NSLog(@"1 == %ld", flag);
}
}
};
void (^negative)() = ^{
while (1) {
@synchronized (@(flag)) {
[NSThread sleepForTimeInterval:1];
flag = -1;
[NSThread sleepForTimeInterval:0.5];
NSLog(@"-1 == %ld", flag);
}
}
};
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), positive);
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), negative);
dispatch_semaphore
dispatch_semaphore 是 GCD 中提供的信号量机制,通过信号量控制互斥访问,提供了 wait 和 signal 方法,类似于信号量的 PV 操作。出于性能考虑建议开发中使用这个作为锁。
- wait 对信号量减一,如小于 0 则等待
- signal 如果没有等待接受信号的线程,则对信号量加一
__block NSInteger flag = 0;
dispatch_semaphore_t semaphore = dispatch_semaphore_create(1);
void (^positive)() = ^{
while (1) {
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
[NSThread sleepForTimeInterval:1];
flag = 1;
[NSThread sleepForTimeInterval:0.5];
NSLog(@"1 == %ld", flag);
dispatch_semaphore_signal(semaphore);
}
};
void (^negative)() = ^{
while (1) {
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
[NSThread sleepForTimeInterval:1];
flag = -1;
[NSThread sleepForTimeInterval:0.5];
NSLog(@"-1 == %ld", flag);
dispatch_semaphore_signal(semaphore);
}
};
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), positive);
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), negative);
OSSpinLock
自旋锁,当无法获得锁时会一直空循环,占用 CPU,性能最高,适合执行轻量级工作,但是有隐藏的线程安全问题。
使用时需要引入包 #import
__block NSInteger flag = 0;
__block OSSpinLock lock = OS_SPINLOCK_INIT;
void (^positive)() = ^{
while (1) {
OSSpinLockLock(&lock);
[NSThread sleepForTimeInterval:1];
flag = 1;
[NSThread sleepForTimeInterval:0.5];
NSLog(@"1 == %ld", flag);
OSSpinLockUnlock(&lock);
[NSThread sleepForTimeInterval:1];
}
};
void (^negative)() = ^{
while (1) {
OSSpinLockLock(&lock);
[NSThread sleepForTimeInterval:1];
flag = -1;
[NSThread sleepForTimeInterval:0.5];
NSLog(@"-1 == %ld", flag);
OSSpinLockUnlock(&lock);
[NSThread sleepForTimeInterval:1];
}
};
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), positive);
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), negative);
pthread_mutex
这是 C 语言中定义的互斥锁,对于未获得锁的线程不使用忙等,而是直接睡眠。
首先引入头文件 #import
然后初始化 lock 变量
pthread_mutexattr_t attr;
pthread_mutexattr_init(&attr);
pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_NORMAL);
__block pthread_mutex_t lock;
pthread_mutex_init(&lock, &attr);
pthread_mutexattr_destroy(&attr);
这里 attr 有几种 type
PTHREAD_MUTEX_NORMAL 缺省类型,也就是普通锁。当一个线程加锁以后,其余请求锁的线程将形成一个等待队列,并在解锁后先进先出原则获得锁。
PTHREAD_MUTEX_ERRORCHECK 检错锁,如果同一个线程请求同一个锁,则返回 EDEADLK,否则与普通锁类型动作相同。这样就保证当不允许多次加锁时不会出现嵌套情况下的死锁。
PTHREAD_MUTEX_RECURSIVE 递归锁,允许同一个线程对同一个锁成功获得多次,并通过多次 unlock 解锁。
PTHREAD_MUTEX_DEFAULT 适应锁,动作最简单的锁类型,仅等待解锁后重新竞争,没有等待队列。
加锁解锁过程类似其他锁
pthread_mutex_lock(&lock);
pthread_mutex_unlock(&lock);
pthread_mutex_destroy 为释放锁资源。