目录
3.dispatch_barrier_async 的工作原理
前言
在 iOS 开发中,数据访问的多线程环境非常普遍,例如缓存系统、数据库查询、文件操作等。对于多读少写的场景,设计一个高效且线程安全的方案至关重要。本文将深入介绍多读少写的设计思想,并结合实际代码示例进行详细讲解。
一、多读少写的特性
1.多读:
读取操作频繁,通常是非阻塞的,并且不会修改数据。
2.少写:
写入操作较少,但会对共享资源进行修改,因此需要确保线程安全。
3.关键需求:
读操作应尽可能高效,不影响主线程。
写操作应保证独占性,防止数据竞争。
二、常见实现方案
1.dispatch_barrier_async(GCD)
在 iOS 和 macOS 的开发中,dispatch_barrier_async 是 GCD(Grand Central Dispatch)提供的一个强大的 API,用于管理并发任务队列,尤其在多读少写的场景中发挥重要作用。
1.dispatch_barrier_async的作用
dispatch_barrier_async 的核心作用是在并发队列中插入一个屏障任务(Barrier Task)。这个屏障任务具有以下特点:
- 确保屏障任务之前的所有任务完成后才会执行屏障任务。
- 确保屏障任务执行时,队列中没有其他任务正在运行。
- 屏障任务执行完毕后,队列恢复并发执行模式。
2.屏障任务的使用场景
1.多读少写
在允许多个线程并发读取的同时,确保写入操作互斥进行。例如:
-
缓存系统的读写操作。
-
数据库查询和更新。
2.任务分组
将任务划分为多个阶段,确保一个阶段完成后才开始下一个阶段。
3.同步操作
在异步任务的执行流中,确保某些关键任务按顺序执行。
这种特性非常适合在多线程场景下,处理多读少写的操作需求。
3.dispatch_barrier_async 的工作原理
屏障任务只有在自定义并发队列(DISPATCH_QUEUE_CONCURRENT)中才能起到屏障作用。
在全局并发队列(DISPATCH_QUEUE_GLOBAL)中使用 dispatch_barrier_async,无法实现屏障效果。
队列执行顺序如下图所示:
[Task 1] -> [Task 2] -> [Barrier Task] -> [Task 3] -> [Task 4]
1. Task 1 和 Task 2 并发执行。
2. Barrier Task 等待 Task 1 和 Task 2 完成后才开始执行。
3. Barrier Task 独占队列,执行时没有其他任务运行。
4. Task 3 和 Task 4 在 Barrier Task 完成后恢复并发执行。
4.示例代码
1.基础用法
以下示例展示了如何通过 dispatch_barrier_async 实现多读少写的线程安全操作:
#import <Foundation/Foundation.h>
@interface BarrierExample : NSObject
@property (nonatomic, strong) dispatch_queue_t concurrentQueue;
@property (nonatomic, strong) NSMutableArray *dataArray;
@end
@implementation BarrierExample
- (instancetype)init {
self = [super init];
if (self) {
// 创建一个并发队列
_concurrentQueue = dispatch_queue_create("com.example.concurrentQueue", DISPATCH_QUEUE_CONCURRENT);
_dataArray = [NSMutableArray array];
}
return self;
}
// 读取操作
- (void)readData {
dispatch_async(self.concurrentQueue, ^{
NSLog(@"Reading data: %@", self.dataArray);
});
}
// 写入操作
- (void)writeData:(id)data {
dispatch_barrier_async(self.concurrentQueue, ^{
[self.dataArray addObject:data];
NSLog(@"Writing data: %@", self.dataArray);
});
}
@end
int main(int argc, const char * argv[]) {
@autoreleasepool {
BarrierExample *example = [[BarrierExample alloc] init];
// 模拟多线程读写
for (int i = 0; i < 5; i++) {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[example readData];
});
}
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[example writeData:@"New Data"];
});
// 延时以确保任务完成
sleep(1);
}
return 0;
}
输出示例:
Reading data: ()
Reading data: ()
Writing data: ("New Data")
Reading data: ("New Data")
Reading data: ("New Data")
读操作并发执行,效率高。
写操作确保独占性,数据安全。
2.应用场景:缓存系统
在缓存系统中,读取数据可以并发进行,但写入需要独占队列,确保数据一致性:
- (id)readFromCache {
__block id result = nil;
dispatch_sync(self.concurrentQueue, ^{
result = [self.cache objectForKey:@"key"];
});
return result;
}
- (void)writeToCache:(id)value {
dispatch_barrier_async(self.concurrentQueue, ^{
[self.cache setObject:value forKey:@"key"];
});
}
5.注意事项
1.屏障任务对队列的影响
只能在自定义并发队列中起作用。
在全局并发队列中不会阻塞其他任务。
2.性能优化
读操作不会阻塞,性能高。
写操作独占队列,适合写入频率较低的场景。
3.不要滥用屏障任务
如果数据写入频繁,屏障任务可能会成为性能瓶颈。
对于写入频繁的场景,考虑其他机制(如 pthread_rwlock 或 NSLock)。
2.pthread_rwlock
pthread_rwlock 是 POSIX 提供的读写锁,专为多读少写场景设计。
它提供两种操作:
- pthread_rwlock_rdlock:加读锁,允许多个线程同时读取。
- pthread_rwlock_wrlock:加写锁,独占资源。
示例代码
#import <Foundation/Foundation.h>
#import <pthread.h>
@interface RWLockExample : NSObject
@property (nonatomic, strong) NSMutableArray *dataArray;
@property (nonatomic) pthread_rwlock_t rwLock;
@end
@implementation RWLockExample
- (instancetype)init {
self = [super init];
if (self) {
_dataArray = [NSMutableArray array];
pthread_rwlock_init(&_rwLock, NULL); // 初始化读写锁
}
return self;
}
- (void)dealloc {
pthread_rwlock_destroy(&_rwLock); // 销毁读写锁
}
// 读取操作
- (void)readData {
pthread_rwlock_rdlock(&_rwLock); // 加读锁
NSLog(@"Reading data: %@", self.dataArray);
pthread_rwlock_unlock(&_rwLock); // 解锁
}
// 写入操作
- (void)writeData:(id)data {
pthread_rwlock_wrlock(&_rwLock); // 加写锁
[self.dataArray addObject:data];
NSLog(@"Writing data: %@", self.dataArray);
pthread_rwlock_unlock(&_rwLock); // 解锁
}
@end
int main(int argc, const char * argv[]) {
@autoreleasepool {
RWLockExample *example = [[RWLockExample alloc] init];
// 模拟多线程读写
for (int i = 0; i < 5; i++) {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[example readData];
});
}
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[example writeData:@"New Data"];
});
sleep(1);
}
return 0;
}
1.优势
高度灵活,可精确控制读写锁的加锁和解锁。
性能优秀,尤其适合高并发读操作。
2.缺点
代码复杂度较高。
手动管理锁容易导致死锁或解锁失败。
三、方案对比
特性 | dispatch_barrier_async | pthread_rwlock |
实现复杂度 | 低 | 高 |
性能 | 适中 | 较高 |
适用场景 | 数据量较小,非实时要求场景 | 数据量较大,高实时性要求场景 |
灵活性 | 较低 | 高 |
推荐程度 | 中 | 高 |
四、总结
在 iOS 多线程开发中,针对多读少写场景,应优先选择性能和实现复杂度平衡的方案。
如果项目简单且代码维护成本较低,推荐使用 GCD 的 dispatch_barrier_async。
如果需要更高的性能,尤其是在高并发场景下,建议使用 pthread_rwlock 进行精细化控制。
掌握这些方案,可以帮助开发者在多线程环境下有效地管理共享资源,提升应用性能和用户体验。