发布于 

iOS 中的锁

我们在使用多线程访问同一块资源时,常会面临数据安全的问题。为了能够保证每次只有一个线程在访问同一块资源,我们需要借助锁。本篇归类记录 iOS 常见的八种锁是什么及怎么样。

自旋锁 & 互斥锁

互斥锁

  • 当上一个线程的任务没有执行完毕时(被锁住),在下一个线程来后会进入休眠状态等待任务执行完毕。在上一个线程的任务执行完毕,下一个线程才会自动唤醒并执行任务。
  • 所谓休眠,即在访问被锁定资源时,调用者线程会休眠,此时 CPU 可以调度其他线程工作。知道被锁资源释放锁,此时自动唤醒休眠线程。

自旋锁

  • 区别于互斥锁,在上一个线程的任务没执行完毕时,下一个线程不会进入睡眠,而是进入忙等待状态(不断地循环检查锁是否可用)。当上一个线程任务执行完毕,下一个线程立即执行任务。
  • 所谓忙等,即在访问被锁资源时,调用者线程不会休眠,而是不停循环检测,知道被锁资源释放锁。

优缺点对比

  • 自旋锁优点在于,自旋锁不会引起调用者休眠,所以不会进行线程调度、CPU 时间片轮转等耗时操作。所以如果能在很短的时间内获得锁,自旋锁的效率会远高于互斥锁。
  • 自旋锁缺点在于,会一直占用 CPU,在被访问资源未解锁时,会一直运转(自旋)占用 CPU。如果不能在很短的时间内获得锁,会使 CPU 效率降低。自旋锁不能实现递归调用

互斥锁:

  • @synchronized
  • NSLock
  • pthread_mutex
  • NSConditionLock
  • NSCondition
  • NSRecursiveLock

自旋锁:

  • atomic
  • OSSPinLock
  • dispatch_semaphore_t

@synchronized

@synchronized 结构所做的事情和锁类似,它可以防止不同的线程同事执行同一段代码。相比较 NSLock(后面再介绍),不需要创建锁对象、不需要加锁解锁,使用方便可读性强。

先看一个 NSLock 的示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
@interface ThreadSafeQueue : NSObject
@end

@implementation ThreadSafeQueue {
NSMutableArray *_elements;
NSLock *_lock;
}

- (instancetype)init {
self = [super init];
if (self) {
_elements = [NSMutableArray array];
_lock = [[NSLock alloc] init];
}
return self;
}

- (void)push:(id)element {
[_lock lock];
[_elements addObject:element];
[_lock unlock];
}

@end

再看下用 @synchronized 结构的实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
@interface ThreadSafeQueue : NSObject
@end

@implementation ThreadSafeQueue {
NSMutableArray *_elements;
}

- (instancetype)init {
self = [super init];
if (self) {
_elements = [NSMutableArray array];
}
return self;
}

- (void)push:(id)element {
// 用 self 或 _elements 效果相同
@synchronized (self) {
[_elements addObject:element];
}
}

@end

NSLock

  • lock
  • unlock
  • trylock:尝试加锁,若成功 return YES,若失败 return NO
  • lockBeforeDate:在 Date 前尝试加锁,若成功 return YES,若失败 return NO

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
NSLock *lock = [NSLock new];
//线程1
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[lock lock];
sleep(3);
[lock unlock];
});

//线程2
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
BOOL isLocked = [lock lockBeforeDate:[NSDate dateWithTimeIntervalSinceNow:4]];
if (isLocked) {
[lock unlock];
} else {
//
}
});

dispatch_semaphore 信号量

  • **dispatch_semaphore_create(1)**:传入值必须 >= 0,若传入 0 会阻塞线程并等待 timeout,时间到后再执行下面的语句。
  • **dispatch_semaphore_wait(signal, overTime)**:加锁,信号量 -1
  • **dispatch_semaphore_signal(signal)**:解锁,信号量 +1

信号量的值可以拿停车位对比理解,信号量(车位)为 0 时,需要等待,信号量(车位) >0 时可以继续调度函数。

示例:

1
2
3
4
5
6
7
dispatch_semaphore_t signal = dispatch_semaphore_create(1);
dispatch_time_t overTime = dispatch_time(DISPATCH_TIME_NOW, 3.0f * NSEC_PER_SEC);

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
dispatch_semaphore_wait(signal, overTime); //signal 值 -1
dispatch_semaphore_signal(signal); //signal 值 +1
});

pthread_mutex

需要带入头文件 #import <pthread.h>

使用示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#import <pthread.h>

static pthread_mutex_t pLock;
pthread_mutex_init(&pLock, NULL);

//1. 线程 1
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
pthread_mutex_lock(&pLock);
sleep(3);
pthread_mutex_unlock(&pLock);
});

//2. 线程 2
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
if (pthread_mutex_trylock(&pLock)) {
pthread_mutex_unlock(&pLock);
}
});

NSConditionLock 条件锁

NSConditionLock 可以实现任务之间的依赖。

示例代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
NSConditionLock *cLock = [[NSConditionLock alloc] initWithCondition:0];

// 线程 1
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
if ([cLock tryLockWhenCondition:0]) {
NSLog(@"OP1");
[cLock unlockWithCondition:1];
} else {
NSLog(@"PFILURE");
}
});

// 线程 2
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[cLock lockWhenCondition:3];
NSLog(@"OP2");
[cLock unlockWithCondition:2];
});

// 线程 3
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[cLock lockWhenCondition:1];
NSLog(@"OP3");
[cLock unlockWithCondition:3];
});

// 打印结果
2016-03-11 15:26:40.737312+0800 Demo[8361:6200252] OP1
2016-03-11 15:26:40.737590+0800 Demo[8361:6200249] OP3
2016-03-11 15:26:40.737880+0800 Demo[8361:6200253] OP2

执行过程:0 -> 1 -> 3 -> 2,所以说可以通过 NSConditionLock 实现任务的依赖。

NSCondition

  • wait:进入等待状态
  • waitUntilDate:等待一定的时间
  • signal:唤醒一个等待的线程
  • broadcase:唤醒所有等待的线程

NSRecursiveLock

递归锁可以被同一个线程多次请求,而不会引起死锁。主要用在循环和递归操作中。

OSSPinLock

使用 OSSpinLock 需要引入头文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
#import <libkern/OSAtomic.h>

__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 已解锁");
NSLog(@"--------------------------------------------------------");
});

//线程2
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSLog(@"线程2 开始上锁");
OSSpinLockLock(&oslock);
NSLog(@"线程2");
OSSpinLockUnlock(&oslock);
NSLog(@"线程2 已解锁");
});

// 打印日志
2016-03-11 15:47:03.879471+0800 BuildDemo[8569:6213056] 线程1 开始上锁
2016-03-11 15:47:03.879471+0800 BuildDemo[8569:6213053] 线程2 开始上锁
2016-03-11 15:47:03.879679+0800 BuildDemo[8569:6213053] 线程2
2016-03-11 15:47:03.879808+0800 BuildDemo[8569:6213053] 线程2 已解锁
2016-03-11 15:47:07.881847+0800 BuildDemo[8569:6213056] 线程1
2016-03-11 15:47:07.882197+0800 BuildDemo[8569:6213056] 线程1 已解锁
2016-03-11 15:47:07.882454+0800 BuildDemo[8569:6213056] --------------------------------------------------------

本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议,转载请注明出处。

本站由 @JonyFang 创建,使用 Stellar 作为主题,您可以在 GitHub 找到本站源码。