iOS中的线程同步方案

NSCondition

  • NSCondition是对mutex和cond的封装

用法

1
2
3
4
5
6
7
@interface NSCondition: NSObject<NSLocking> {
- (void)wait;
- (BOOL)waitUntilDate:(NSDate *)limit;
- (void)signal;
- (void)broadcast;
}
@end

示例:

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
#import "NSConditionDemo.h"

@interface NSConditionDemo()
@property (strong, nonatomic) NSCondition *condition;
@property (strong, nonatomic) NSMutableArray *data;
@end

@implementation NSConditionDemo

- (instancetype)init {
if (self = [super init]) {
self.condition = [[NSCondition alloc] init];
self.data = [NSMutableArray array];
}
return self;
}

- (void)otherTest {
[[[NSThread alloc] initWithTarget:self selector:@selector(__remove) object:nil] start];

[[[NSThread alloc] initWithTarget:self selector:@selector(__add) object:nil] start];
}

// 生产者-消费者模式

// 线程1
// 删除数组中的元素
- (void)__remove {
[self.condition lock];
NSLog(@"__remove - begin");

if (self.data.count == 0) {
// 等待
[self.condition wait];
}

[self.data removeLastObject];
NSLog(@"删除了元素");

[self.condition unlock];
}

// 线程2
// 往数组中添加元素
- (void)__add {
[self.condition lock];

sleep(1);

[self.data addObject:@"Test"];
NSLog(@"添加了元素");

// 信号
[self.condition signal];
// 广播
//[self.condition broadcast];

sleep(2);

[self.condition unlock];
}
@end

NSConditionLock

  • NSConditionLock是对NSCondition的进一步封装,可以设置具体的条件值
  • 线程之间的依赖

用法

1
2
3
4
5
6
7
8
9
10
11
@interface NSConditionLock: NSObject<NSLocking> {
@property (readonly) NSInteger condition;
- (instancetype)initWithCondition:(NSInteger)condition;
- (void)lockWhenCondition:(NSInteger)condition;
- (BOOL)tryLock;
- (BOOL)tryLockWhenCondition:(NSInteger)condition;
- (void)unLockWhenCondition:(NSInteger)condition;
- (BOOL)lockBeforeDate:(NSDate *)limit;
- (BOOL)lockWhenCondition:(NSInteger)condition beforeDate:(NSDate *)limit;
}
@end

示例:

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 "NSConditionLockDemo.h"

@interface NSConditionLockDemo()
@property (strong, nonatomic) NSConditionLock *conditionLock;
@end

@implementation NSConditionLockDemo

- (instancetype)init {
if (self = [super init]) {
//condition默认是从0开始
self.conditionLock = [[NSConditionLock alloc] initWithCondition:1];
}
return self;
}

- (void)otherTest {
[[[NSThread alloc] initWithTarget:self selector:@selector(__one) object:nil] start];

[[[NSThread alloc] initWithTarget:self selector:@selector(__two) object:nil] start];

[[[NSThread alloc] initWithTarget:self selector:@selector(__three) object:nil] start];
}

- (void)__one {
[self.conditionLock lock];

NSLog(@"__one");
sleep(1);

[self.conditionLock unlockWithCondition:2];
}

- (void)__two {
[self.conditionLock lockWhenCondition:2];

NSLog(@"__two");
sleep(1);

[self.conditionLock unlockWithCondition:3];
}

- (void)__three {
[self.conditionLock lockWhenCondition:3];

NSLog(@"__three");

[self.conditionLock unlock];
}
@end

dispatch_semaphore

  • semaphore叫做”信号量”
  • 信号量的初始值,可以用来控制线程并发访问的最大数量
  • 信号量的初始值为1,代表同时只允许1条线程访问资源,保证线程同步

用法

1
2
3
4
5
6
7
8
9
// 信号量的初始值
int value = 1;
// 初始化信号量
dispatch_semaphore_t semaphore = dispatch_semaphore_create(value);
// 如果信号量的值<0,当前线程就会进入休眠等待(直到信号量的值>0)
// 如果信号量的值>0,就减1,然后往下执行后面的代码
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
// 让信号是的值加1
dispatch_semaphore_signal(semaphore);

示例:

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
#import "SemaphoreDemo.h"

@interface SemaphoreDemo()
@property (strong, nonatomic) dispatch_semaphore_t semaphore;
@property (strong, nonatomic) dispatch_semaphore_t ticketSemaphore;
@property (strong, nonatomic) dispatch_semaphore_t moneySemaphore;
@end

@implementation SemaphoreDemo

- (instancetype)init {
if (self = [super init]) {
self.semaphore = dispatch_semaphore_create(5);
self.ticketSemaphore = dispatch_semaphore_create(1);
self.moneySemaphore = dispatch_semaphore_create(1);
}
return self;
}

- (void)__drawMoney {
dispatch_semaphore_wait(self.moneySemaphore, DISPATCH_TIME_FOREVER);

[super __drawMoney];

dispatch_semaphore_signal(self.moneySemaphore);
}

- (void)__saveMoney {
dispatch_semaphore_wait(self.moneySemaphore, DISPATCH_TIME_FOREVER);

[super __saveMoney];

dispatch_semaphore_signal(self.moneySemaphore);
}

- (void)__saleTicket {
dispatch_semaphore_wait(self.ticketSemaphore, DISPATCH_TIME_FOREVER);

[super __saleTicket];

dispatch_semaphore_signal(self.ticketSemaphore);
}

- (void)otherTest {
for (int i = 0; i < 20; i++) {
[[[NSThread alloc] initWithTarget:self selector:@selector(test) object:nil] start];
}
}

// 线程10、7、6、9、8
- (void)test {
// 如果信号量的值 > 0,就让信号量的值减1,然后继续往下执行代码
// 如果信号量的值 <= 0,就会休眠等待,直到信号量的值变成>0,就让信号量的值减1,然后继续往下执行代码
dispatch_semaphore_wait(self.semaphore, DISPATCH_TIME_FOREVER);

sleep(2);
NSLog(@"test - %@", [NSThread currentThread]);

// 让信号量的值+1
dispatch_semaphore_signal(self.semaphore);
}
@end

dispatch_queue

  • 直接使用GCD的串行队列,也是可以实现线程同步的

用法

1
2
3
4
dispatch_queue_t = dispatch_queue_create("lock_queue", DISPATCH_QUEEU_SERIAL);
dispatch_sync(queue, ^{
//任务...
})

示例:

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 "SerialQueueDemo.h"

@interface SerialQueueDemo()
@property (strong, nonatomic) dispatch_queue_t ticketQueue;
@property (strong, nonatomic) dispatch_queue_t moneyQueue;
@end

@implementation SerialQueueDemo

- (instancetype)init {
if (self = [super init]) {
self.ticketQueue = dispatch_queue_create("ticketQueue", DISPATCH_QUEUE_SERIAL);
self.moneyQueue = dispatch_queue_create("moneyQueue", DISPATCH_QUEUE_SERIAL);
}
return self;
}

- (void)__drawMoney {
dispatch_sync(self.moneyQueue, ^{
[super __drawMoney];
});
}

- (void)__saveMoney {
dispatch_sync(self.moneyQueue, ^{
[super __saveMoney];
});
}

- (void)__saleTicket {
dispatch_sync(self.ticketQueue, ^{
[super __saleTicket];
});
}
@end

@synchronized

  • @synchronized是对mutex递归锁的封装
  • 源码查看:objc4中的objc-sync.mm文件
  • @synchronized(obj)内部会生成obj对应的递归锁,然后进行加锁、解锁操作

用法

1
2
3
@synchronized(obj) {
//任务
}

示例:

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 "SynchronizedDemo.h"

@implementation SynchronizedDemo

- (void)__drawMoney {
@synchronized([self class]) {
[super __drawMoney];
}
}

- (void)__saveMoney {
@synchronized([self class]) { // objc_sync_enter
[super __saveMoney];
} // objc_sync_exit
}

- (void)__saleTicket {
static NSObject *lock;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
lock = [[NSObject alloc] init];
});

@synchronized(lock) {
[super __saleTicket];
}
}

- (void)otherTest {
@synchronized([self class]) {
NSLog(@"123");
[self otherTest];
}
}
@end

iOS中的读写安全方案

  • 思考如何实现以下场景
    • 同一时间,只能有1个线程进行写的操作
    • 同一时间,允许有多个线程进行读的操作
    • 同一时间,不允许既有写的操作,又有读的操作

上面的场景就是典型的“多读单写”,经常用于文件等数据的读写操作,iOS中的实现方案有

  • pthread_rwlock:读写锁
  • dispatch_barrier_async:异步栅栏调用

pthread_rwlock

  • 等待锁的线程会进入休眠

用法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 初始化锁
pthread_rwlock lock;
pthread_rwlock_init(&lock, NULL);
// 读 - 加锁
pthread_rwlock_rdlock(&lock);
// 读 - 尝试加锁
pthread_rwlock_tryrdlock(&lock);
// 写 - 加锁
pthread_rwlock_wrlock(&lock);
// 写 - 尝试加锁
pthread_rwlock_trywrlock(&lock);
// 解锁
pthread_rwlock_unlock(&lock):
// 销毁
pthread_rwlock_destroy(&lock):

示例:

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
#import "ViewController.h"
#import <pthread.h>

@interface ViewController ()
@property (assign, nonatomic) pthread_rwlock_t lock;
@end

@implementation ViewController

- (void)viewDidLoad {
[super viewDidLoad];

// 初始化锁
pthread_rwlock_init(&_lock, NULL);

dispatch_queue_t queue = dispatch_get_global_queue(0, 0);

for (int i = 0; i < 10; i++) {
dispatch_async(queue, ^{
[self read];
});
dispatch_async(queue, ^{
[self write];
});
}
}

- (void)read {
pthread_rwlock_rdlock(&_lock);

sleep(1);
NSLog(@"%s", __func__);

pthread_rwlock_unlock(&_lock);
}

- (void)write {
pthread_rwlock_wrlock(&_lock);

sleep(1);
NSLog(@"%s", __func__);

pthread_rwlock_unlock(&_lock);
}

- (void)dealloc {
pthread_rwlock_destroy(&_lock);
}
@end

dispatch_barrier_async

  • 这个函数传入的并发队列必须是自己通过dispatch_queue_cretate创建的
  • 如果传入的是一个串行或是一个全局的并发队列,那这个函数便等同于dispatch_async函数的效果

用法

1
2
3
4
5
6
7
8
9
10
// 初始化队列
dispatch_queue_t queue = dispatch_queue_create("rw_queue", DISPATCH_QUEUE_CONCURRENT);
// 读
dispatch_async(self.queue, ^{

});
// 写
dispatch_barrier_async(self.queue, ^{

});

示例:

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 "ViewController.h"
#import <pthread.h>

@interface ViewController ()
@property (strong, nonatomic) dispatch_queue_t queue;
@end

@implementation ViewController

- (void)viewDidLoad {
[super viewDidLoad];

// NSOperationQueue *queue = [[NSOperationQueue alloc] init];
// queue.maxConcurrentOperationCount = 5;

// dispatch_semaphore_create(5);

self.queue = dispatch_queue_create("rw_queue", DISPATCH_QUEUE_CONCURRENT);

for (int i = 0; i < 10; i++) {
dispatch_async(self.queue, ^{
[self read];
});

dispatch_async(self.queue, ^{
[self read];
});

dispatch_async(self.queue, ^{
[self read];
});

//添加栅栏锁
dispatch_barrier_async(self.queue, ^{
[self write];
});
}
}

- (void)read {
sleep(1);
NSLog(@"read");
}

- (void)write {
sleep(1);
NSLog(@"write");
}

@end

atomic

  • atomic用于保证属性setter、getter的原子性操作,相当于在getter和setter内部加了线程同步的锁
  • 可以参考源码objc4的objc-accessors.mm
  • 它并不能保证使用属性的过程是线程安全的

iOS线程同步方案性能比较

性能从高到低排序

  • os_unfair_lock
  • OSSpinLock
  • dispatch_semaphore
  • pthread_mutex
  • dispatch_queue(DISPATCH_QUEUE_SERIAL)
  • NSLock
  • NSCondition
  • pthread_mutex(recursive)
  • NSRecursiveLock
  • NSConditionLock
  • @synchronized

自旋锁、互斥锁比较

什么情况使用自旋锁比较划算?

  • 预计线程等待锁的时间很短
  • 加锁的代码(临界区)经常被调用,但竞争情况很少发生
  • CPU资源不紧张
  • 多核处理器

什么情况使用互斥锁比较划算?

  • 预计线程等待锁的时间较长
  • 单核处理器
  • 临界区有IO操作
  • 临界区代码复杂或者循环量大
  • 临界区竞争非常激烈

高频面试题

  • 你理解的多线程?
  • iOS的多线程方案有哪几种?你更倾向于哪一种?
  • 你在项目中用过 GCD 吗?
  • GCD 的队列类型
  • 说一下 OperationQueue 和 GCD 的区别,以及各自的优势
  • 线程安全的处理手段有哪些?
  • OC你了解的锁有哪些?在你回答基础上进行二次提问;
    • 追问一:自旋和互斥对比?
    • 追问二:使用以上锁需要注意哪些?
    • 追问三:用C/OC/C++,任选其一,实现自旋或互斥?口述即可!

参考