iOS多线程之多读单写和常驻线程

熟悉多线程的朋友,应该都遇到过这样的应用场景:

  • 多个线程去读写数据。那么试想一下如果一条线程正在读取数据的时候另外一条线程同时在写数据。这种场景是否会产生问题呢?

  • 我们期望这样一个线程,可以被我们反复利用,那么这又是如何实现的呢?

接下来我们一一介绍

如何解决多个线程对同一块资源的读写问题呢?

试想一下,当一个线程在读取资源的时候,正好有另外一个线程修改了这个资源,或者多个线程同时修改资源,这两种情况都会导致数据错乱。所以为了避免这种问题的产生我们应该怎么处理呢?

我们在多线程读写的时候应该有以下原则:

  • 读者、读者并发
  • 读者、写者互斥
  • 写者、写者互斥

满足以上原则,我们就可以很好的结局上述问题。满足以上原则的话,系统给我们提供了很好用的API:dispatch_barrier_async,它可以很好的将我们的读写操作分离,原则上我们允许多个读取线程同时读取数据,假如说有写线程的话,通过上述dispatch_barrier_async就可以将它之前的读操作隔离开来,同样的如果它后面还有读取线程的话也必须等到,写数据线程执行完之后才会执行。

具体的用法呢,就是通过调用dispatch_barrier_async将写任务提交到一个自定义的并发队列concurrent_queue中,而读取操作呢用过调用dispatch_sync将读取操作以同步的方式提交到自定义的并发队列concurrent_queue中。

接下来我们看具体的实现:

我们声明一个类MultiReadSingle Write

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
// 接口部分
@interface MultiReadSingleWrite : NSObject

- (id)objectForKey:(NSString *)key;
- (void)setObject:(id)object forKey:(NSString *)key;

@end

// 实现部分
@interface MultiReadSingleWrite (){
// 定义一个并发队列
dispatch_queue_t concurrent_queue;
// 定义一个供线程读写的数据容器
NSMutableDictionary *containerDict;
}

@end

@implementation MultiReadSingleWrite

- (instancetype)init {
if (self = [super init]) {
// 定义一个访问容器
containerDict = [[NSMutableDictionary alloc]initWithCapacity:0];
// 创件一个并发队列
concurrent_queue = dispatch_queue_create("read_write_queue", DISPATCH_QUEUE_CONCURRENT);
}
return self;
}

// 读取数据
- (id)objectForKey:(NSString *)key {
__block id value = nil;
// 同步读取数据
dispatch_sync(concurrent_queue, ^{
value = [containerDict objectForKey:key];
});
return value;
}

// 写数据
- (void)setObject:(id)object forKey:(NSString *)key {
// 异步栅栏调用设置数据
dispatch_barrier_async(concurrent_queue, ^{
[containerDict setObject:object forKey:key];
});
}

@end

没错,就是这么简单,相信你看了我的介绍,对多读单写应该很清楚了。接下来我们来介绍上述第二个问题。

如何创建一个可以反复使用的线程呢?

反复使用的线程说白了就是我们通常所说的 常驻线程。那么改如何创建呢?需要满足一下三点:

  • 为当前线程开启一个RunLoop
  • 向该RunLoop中添加一个Port/Sources等维持RunLoop的事件循环
  • 启动该RunLoop

我们看代码实现:

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
// 接口部分
@interface AliveForeverThread : NSObject
+ (NSThread *)threadForDispatch;
@end

// 实现部分
static NSThread *thred = nil;
static BOOL runAlways = YES;
@implementation AliveForeverThread

+ (NSThread *)threadForDispatch {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
thred = [[NSThread alloc]initWithTarget:self selector:@selector(runRequest) object:nil];
[thred setName:@"charles_alive_thread"];
[thred start];
});
return thred;
}

+ (void)runRequest {
// 创建一个Source
CFRunLoopSourceContext context = {0, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL};
CFRunLoopSourceRef source = CFRunLoopSourceCreate(kCFAllocatorDefault, 0, &context);

// 创建RunLoop
CFRunLoopAddSource(CFRunLoopGetCurrent(), source,kCFRunLoopDefaultMode);

// 开始运行
while (runAlways) {
@autoreleasepool {
// 使当前RunLoop运行在DefaultMode下。
CFRunLoopRunInMode(kCFRunLoopDefaultMode, 1.0e10, true);
}
}

// 某一时机,runAlways = NO 时,可以保证跳出RunLoop,线程退出。
CFRunLoopRemoveSource(CFRunLoopGetCurrent(), source, kCFRunLoopDefaultMode);
CFRelease(source);
}

@end

到这里,我们上述的两个问题就介绍完了,欢迎勘误!

谢谢您的支持!