引言

在iOS开发里,多线程编程能显著提升应用性能和响应能力。不过,多线程环境下对共享资源的并发访问容易引发数据竞争和不一致问题。线程锁作为保障线程安全的关键工具,能有效避免这些问题。本文会深入探讨Objective - C中各种线程锁的底层原理、使用方法和适用场景,还会介绍信号量和串行队列实现线程同步的方法,同时给出示例代码辅助理解。

一、线程安全问题概述

多线程编程时,多个线程同时访问和修改共享资源,就可能出现数据竞争。例如,多个线程同时对一个变量进行自增操作,可能会导致最终结果与预期不符。下面是一个简单的示例:

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
32
33
34
35
#import <Foundation/Foundation.h>

@interface ThreadSafeExample : NSObject
@property (nonatomic, assign) NSInteger counter;
@end

@implementation ThreadSafeExample

- (void)incrementCounter {
for (int i = 0; i < 10000; i++) {
self.counter++;
}
}

@end

int main(int argc, const char * argv[]) {
@autoreleasepool {
ThreadSafeExample *example = [[ThreadSafeExample alloc] init];

dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

dispatch_async(queue, ^{
[example incrementCounter];
});

dispatch_async(queue, ^{
[example incrementCounter];
});

sleep(2);
NSLog(@"Counter value: %ld", (long)example.counter);
}
return 0;
}

上述代码中,两个线程同时对 counter 变量进行自增操作,由于自增操作不是原子操作,可能会出现数据竞争,导致最终结果小于预期的 20000。

二、常见的线程锁

2.1 @synchronized

2.1.1 原理

@synchronized 是一种简单易用的互斥锁,它基于对象实现。当一个线程进入 @synchronized 代码块时,会尝试获取该对象的锁,如果锁已被其他线程持有,该线程会被阻塞,直到锁被释放。

2.1.2 使用示例

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
32
33
34
35
36
37
#import <Foundation/Foundation.h>

@interface SynchronizedExample : NSObject
@property (nonatomic, assign) NSInteger counter;
@end

@implementation SynchronizedExample

- (void)incrementCounter {
for (int i = 0; i < 10000; i++) {
@synchronized(self) {
self.counter++;
}
}
}

@end

int main(int argc, const char * argv[]) {
@autoreleasepool {
SynchronizedExample *example = [[SynchronizedExample alloc] init];

dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

dispatch_async(queue, ^{
[example incrementCounter];
});

dispatch_async(queue, ^{
[example incrementCounter];
});

sleep(2);
NSLog(@"Counter value: %ld", (long)example.counter);
}
return 0;
}

2.1.3 优缺点

  • 优点:使用简单,无需手动管理锁的生命周期。
  • 缺点:性能相对较低,因为每次进入和退出代码块都有额外的开销;而且只能用于Objective - C对象。

2.2 NSLock

2.2.1 原理

NSLockNSLocking 协议的具体实现,它是一个互斥锁。通过 lock 方法获取锁,unlock 方法释放锁。

2.2.2 使用示例

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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
#import <Foundation/Foundation.h>

@interface NSLockExample : NSObject
@property (nonatomic, assign) NSInteger counter;
@property (nonatomic, strong) NSLock *lock;
@end

@implementation NSLockExample

- (instancetype)init {
self = [super init];
if (self) {
self.lock = [[NSLock alloc] init];
}
return self;
}

- (void)incrementCounter {
for (int i = 0; i < 10000; i++) {
[self.lock lock];
self.counter++;
[self.lock unlock];
}
}

@end

int main(int argc, const char * argv[]) {
@autoreleasepool {
NSLockExample *example = [[NSLockExample alloc] init];

dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

dispatch_async(queue, ^{
[example incrementCounter];
});

dispatch_async(queue, ^{
[example incrementCounter];
});

sleep(2);
NSLog(@"Counter value: %ld", (long)example.counter);
}
return 0;
}

2.2.3 优缺点

  • 优点:使用方便,性能比 @synchronized 略高。
  • 缺点:需要手动管理锁的获取和释放,若忘记释放锁会导致死锁。

2.3 NSRecursiveLock

2.3.1 原理

NSRecursiveLock 是一种递归锁,允许同一个线程多次获取该锁而不会产生死锁。当线程第一次获取锁时,锁的计数加 1,之后该线程再次获取锁,计数继续增加,只有当计数减为 0 时,锁才会被真正释放。

2.3.2 使用示例

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
32
33
34
35
36
37
38
39
40
41
42
43
#import <Foundation/Foundation.h>

@interface NSRecursiveLockExample : NSObject
@property (nonatomic, assign) NSInteger counter;
@property (nonatomic, strong) NSRecursiveLock *recursiveLock;
@end

@implementation NSRecursiveLockExample

- (instancetype)init {
self = [super init];
if (self) {
self.recursiveLock = [[NSRecursiveLock alloc] init];
}
return self;
}

- (void)recursiveFunction {
[self.recursiveLock lock];
self.counter++;
if (self.counter < 10) {
[self recursiveFunction];
}
[self.recursiveLock unlock];
}

@end

int main(int argc, const char * argv[]) {
@autoreleasepool {
NSRecursiveLockExample *example = [[NSRecursiveLockExample alloc] init];

dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

dispatch_async(queue, ^{
[example recursiveFunction];
});

sleep(2);
NSLog(@"Counter value: %ld", (long)example.counter);
}
return 0;
}

2.3.3 优缺点

  • 优点:适用于递归调用的场景,避免死锁。
  • 缺点:同样需要手动管理锁的获取和释放,使用不当也可能导致问题。

2.4 NSCondition

2.4.1 原理

NSCondition 是一种条件锁,它结合了锁和条件变量的功能。线程可以在满足特定条件时等待,当条件满足时被唤醒。

2.4.2 使用示例

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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
#import <Foundation/Foundation.h>

@interface NSConditionExample : NSObject
@property (nonatomic, assign) NSInteger counter;
@property (nonatomic, strong) NSCondition *condition;
@end

@implementation NSConditionExample

- (instancetype)init {
self = [super init];
if (self) {
self.condition = [[NSCondition alloc] init];
self.counter = 0;
}
return self;
}

- (void)producer {
for (int i = 0; i < 10; i++) {
[self.condition lock];
while (self.counter >= 5) {
[self.condition wait];
}
self.counter++;
NSLog(@"Produced: %ld", (long)self.counter);
[self.condition signal];
[self.condition unlock];
}
}

- (void)consumer {
for (int i = 0; i < 10; i++) {
[self.condition lock];
while (self.counter <= 0) {
[self.condition wait];
}
self.counter--;
NSLog(@"Consumed: %ld", (long)self.counter);
[self.condition signal];
[self.condition unlock];
}
}

@end

int main(int argc, const char * argv[]) {
@autoreleasepool {
NSConditionExample *example = [[NSConditionExample alloc] init];

dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

dispatch_async(queue, ^{
[example producer];
});

dispatch_async(queue, ^{
[example consumer];
});

sleep(5);
}
return 0;
}

2.4.3 优缺点

  • 优点:可以实现复杂的线程同步,如生产者 - 消费者模型。
  • 缺点:使用相对复杂,需要仔细管理条件和锁。

2.5 NSConditionLock

2.5.1 原理

NSConditionLock 是基于 NSCondition 实现的条件锁,它允许线程在特定条件下获取锁。线程可以指定一个条件值,只有当锁的当前条件值与指定的条件值匹配时,线程才能获取锁。

2.5.2 使用示例

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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
#import <Foundation/Foundation.h>

@interface NSConditionLockExample : NSObject
@property (nonatomic, strong) NSConditionLock *conditionLock;
@end

@implementation NSConditionLockExample

- (instancetype)init {
self = [super init];
if (self) {
self.conditionLock = [[NSConditionLock alloc] initWithCondition:0];
}
return self;
}

- (void)thread1 {
[self.conditionLock lockWhenCondition:0];
NSLog(@"Thread 1: Condition 0 met");
[self.conditionLock unlockWithCondition:1];
}

- (void)thread2 {
[self.conditionLock lockWhenCondition:1];
NSLog(@"Thread 2: Condition 1 met");
[self.conditionLock unlockWithCondition:0];
}

@end

int main(int argc, const char * argv[]) {
@autoreleasepool {
NSConditionLockExample *example = [[NSConditionLockExample alloc] init];

dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

dispatch_async(queue, ^{
[example thread1];
});

dispatch_async(queue, ^{
[example thread2];
});

sleep(2);
}
return 0;
}

2.5.3 优缺点

  • 优点:可以根据不同的条件进行线程同步,提供更灵活的控制。
  • 缺点:使用场景相对较窄,需要合理设计条件值。

2.6 pthread_mutex_t

2.6.1 原理

pthread_mutex_t 是基于 POSIX 线程库的互斥锁,是一种底层的锁机制。它的性能较高,适用于对性能要求较高的场景。

2.6.2 使用示例

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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
#import <Foundation/Foundation.h>
#import <pthread.h>

@interface PthreadMutexExample : NSObject
@property (nonatomic, assign) NSInteger counter;
@property (nonatomic, assign) pthread_mutex_t mutex;
@end

@implementation PthreadMutexExample

- (instancetype)init {
self = [super init];
if (self) {
pthread_mutex_init(&_mutex, NULL);
self.counter = 0;
}
return self;
}

- (void)incrementCounter {
for (int i = 0; i < 10000; i++) {
pthread_mutex_lock(&_mutex);
self.counter++;
pthread_mutex_unlock(&_mutex);
}
}

- (void)dealloc {
pthread_mutex_destroy(&_mutex);
}

@end

int main(int argc, const char * argv[]) {
@autoreleasepool {
PthreadMutexExample *example = [[PthreadMutexExample alloc] init];

dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

dispatch_async(queue, ^{
[example incrementCounter];
});

dispatch_async(queue, ^{
[example incrementCounter];
});

sleep(2);
NSLog(@"Counter value: %ld", (long)example.counter);
}
return 0;
}

2.6.3 优缺点

  • 优点:性能高,可定制性强。
  • 缺点:使用相对复杂,需要手动管理锁的初始化和销毁。

2.7 OSSpinLock(已弃用)

2.7.1 原理

OSSpinLock 是一种自旋锁,线程在获取锁时会不断尝试,直到获取到锁为止。自旋锁适用于锁持有时间较短的场景。

2.7.2 使用示例(仅作历史参考)

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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
#import <Foundation/Foundation.h>
#import <libkern/OSAtomic.h>

@interface OSSpinLockExample : NSObject
@property (nonatomic, assign) NSInteger counter;
@property (nonatomic, assign) OSSpinLock spinLock;
@end

@implementation OSSpinLockExample

- (instancetype)init {
self = [super init];
if (self) {
self.spinLock = OS_SPINLOCK_INIT;
self.counter = 0;
}
return self;
}

- (void)incrementCounter {
for (int i = 0; i < 10000; i++) {
OSSpinLockLock(&_spinLock);
self.counter++;
OSSpinLockUnlock(&_spinLock);
}
}

@end

int main(int argc, const char * argv[]) {
@autoreleasepool {
OSSpinLockExample *example = [[OSSpinLockExample alloc] init];

dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

dispatch_async(queue, ^{
[example incrementCounter];
});

dispatch_async(queue, ^{
[example incrementCounter];
});

sleep(2);
NSLog(@"Counter value: %ld", (long)example.counter);
}
return 0;
}

2.7.3 优缺点及弃用原因

  • 优点:性能高,适用于锁持有时间短的场景。
  • 缺点:存在优先级反转问题,可能导致低优先级线程长时间持有锁,影响系统性能。因此,苹果已弃用该锁。

2.8 os_unfair_lock

2.8.1 原理

os_unfair_lock 是苹果为替代 OSSpinLock 引入的锁机制,它是一种互斥锁,当线程无法获取锁时会进入休眠状态,避免了自旋锁的优先级反转问题。

2.8.2 使用示例

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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
#import <Foundation/Foundation.h>
#import <os/lock.h>

@interface OSUnfairLockExample : NSObject
@property (nonatomic, assign) NSInteger counter;
@property (nonatomic, assign) os_unfair_lock unfairLock;
@end

@implementation OSUnfairLockExample

- (instancetype)init {
self = [super init];
if (self) {
self.unfairLock = OS_UNFAIR_LOCK_INIT;
self.counter = 0;
}
return self;
}

- (void)incrementCounter {
for (int i = 0; i < 10000; i++) {
os_unfair_lock_lock(&_unfairLock);
self.counter++;
os_unfair_lock_unlock(&_unfairLock);
}
}

@end

int main(int argc, const char * argv[]) {
@autoreleasepool {
OSUnfairLockExample *example = [[OSUnfairLockExample alloc] init];

dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

dispatch_async(queue, ^{
[example incrementCounter];
});

dispatch_async(queue, ^{
[example incrementCounter];
});

sleep(2);
NSLog(@"Counter value: %ld", (long)example.counter);
}
return 0;
}

2.8.3 优缺点

  • 优点:解决了优先级反转问题,性能也比较高。
  • 缺点:使用相对底层,需要对锁的操作有一定了解。

三、信号量实现线程同步

3.1 原理

信号量是一种更高级的同步机制,它可以控制同时访问某个资源的线程数量。信号量有一个初始值,线程在访问资源前需要先请求信号量,如果信号量的值大于 0,则线程可以继续执行,同时信号量的值减 1;如果信号量的值为 0,则线程需要等待,直到有其他线程释放信号量。

3.2 使用示例

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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
#import <Foundation/Foundation.h>

@interface SemaphoreExample : NSObject
@property (nonatomic, assign) NSInteger counter;
@property (nonatomic, strong) dispatch_semaphore_t semaphore;
@end

@implementation SemaphoreExample

- (instancetype)init {
self = [super init];
if (self) {
self.counter = 0;
// 初始化信号量,初始值为 1 表示只允许一个线程同时访问
self.semaphore = dispatch_semaphore_create(1);
}
return self;
}

- (void)incrementCounter {
for (int i = 0; i < 10000; i++) {
// 请求信号量
dispatch_semaphore_wait(self.semaphore, DISPATCH_TIME_FOREVER);
self.counter++;
// 释放信号量
dispatch_semaphore_signal(self.semaphore);
}
}

@end

int main(int argc, const char * argv[]) {
@autoreleasepool {
SemaphoreExample *example = [[SemaphoreExample alloc] init];

dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

dispatch_async(queue, ^{
[example incrementCounter];
});

dispatch_async(queue, ^{
[example incrementCounter];
});

sleep(2);
NSLog(@"Counter value: %ld", (long)example.counter);
}
return 0;
}

3.3 优缺点

  • 优点:可以灵活控制并发线程的数量,适用于需要限制资源访问数量的场景。
  • 缺点:使用相对复杂,需要对信号量的操作有一定理解。

四、串行队列实现线程同步

4.1 原理

串行队列一次只能执行一个任务,当多个线程将任务提交到串行队列时,这些任务会按照提交的顺序依次执行,从而避免了多个线程同时访问共享资源的问题。

4.2 使用示例

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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
#import <Foundation/Foundation.h>

@interface SerialQueueExample : NSObject
@property (nonatomic, assign) NSInteger counter;
@property (nonatomic, strong) dispatch_queue_t serialQueue;
@end

@implementation SerialQueueExample

- (instancetype)init {
self = [super init];
if (self) {
self.counter = 0;
// 创建串行队列
self.serialQueue = dispatch_queue_create("com.example.serialQueue", DISPATCH_QUEUE_SERIAL);
}
return self;
}

- (void)incrementCounter {
dispatch_async(self.serialQueue, ^{
for (int i = 0; i < 10000; i++) {
self.counter++;
}
});
}

@end

int main(int argc, const char * argv[]) {
@autoreleasepool {
SerialQueueExample *example = [[SerialQueueExample alloc] init];

dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

dispatch_async(queue, ^{
[example incrementCounter];
});

dispatch_async(queue, ^{
[example incrementCounter];
});

sleep(2);
NSLog(@"Counter value: %ld", (long)example.counter);
}
return 0;
}

4.3 优缺点

  • 优点:使用简单,不需要手动管理锁的获取和释放。
  • 缺点:性能相对较低,因为任务是串行执行的,不能充分利用多核 CPU 的优势。

五、线程锁的性能比较

不同的线程锁在性能上有所差异,一般来说,底层的锁(如 pthread_mutex_tos_unfair_lock)性能较高,而高层的锁(如 @synchronizedNSLock)使用方便但性能相对较低。信号量和串行队列在不同场景下也有各自的性能特点,需要根据具体需求选择合适的同步方式。

六、死锁问题及避免方法

6.1 死锁的概念

死锁是指两个或多个线程在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力作用,它们都将无法推进下去。

6.2 死锁的示例

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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
#import <Foundation/Foundation.h>

@interface DeadlockExample : NSObject
@property (nonatomic, strong) NSLock *lock1;
@property (nonatomic, strong) NSLock *lock2;
@end

@implementation DeadlockExample

- (instancetype)init {
self = [super init];
if (self) {
self.lock1 = [[NSLock alloc] init];
self.lock2 = [[NSLock alloc] init];
}
return self;
}

- (void)thread1 {
[self.lock1 lock];
NSLog(@"Thread 1: Lock 1 acquired");
sleep(1);
[self.lock2 lock];
NSLog(@"Thread 1: Lock 2 acquired");
[self.lock2 unlock];
[self.lock1 unlock];
}

- (void)thread2 {
[self.lock2 lock];
NSLog(@"Thread 2: Lock 2 acquired");
sleep(1);
[self.lock1 lock];
NSLog(@"Thread 2: Lock 1 acquired");
[self.lock1 unlock];
[self.lock2 unlock];
}

@end

int main(int argc, const char * argv[]) {
@autoreleasepool {
DeadlockExample *example = [[DeadlockExample alloc] init];

dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

dispatch_async(queue, ^{
[example thread1];
});

dispatch_async(queue, ^{
[example thread2];
});

sleep(5);
}
return 0;
}

6.3 避免死锁的方法

  • 按顺序加锁:所有线程都按照相同的顺序获取锁,避免循环等待。
  • 使用超时机制:在获取锁时设置超时时间,若超时则放弃获取锁,避免无限等待。
  • 减少锁的持有时间:尽量缩短锁的持有时间,减少死锁的可能性。

七、总结

线程锁、信号量和串行队列在多线程编程中起着至关重要的作用,不同的同步机制有不同的特点和适用场景。在实际开发中,需要根据具体的需求选择合适的同步方式,并注意避免死锁等问题。希望本文能帮助你深入理解Objective - C中的线程同步机制,在开发中编写出更安全、高效的多线程代码。

参考资料