我们是使用FMDB保存好友,联系人数据,在开始使用FMDB的进行小批量数据的读写时,开始还是蛮正常的,随着数据量以及业务的复杂增加,发现了一些离奇的问题:
1、偶现联系人数据表中存在重复记录;
2、偶现读取不到数据,但拉数据库里面却有数据;
根据业务场景分析,确实存在并发读写的情况,由于我们使用的是单例模式,所以问题1在不进行多线程互斥访问的情况下,确实是存在这个问题,所以想到的思路是将所有读写操作都放到一个队列中,执行完成了在通知UI获取数据,这个想法竟然和FMDatabaseQueue的思路是一样的,但网上说FMDatabaseQueue还是存在线程安全的问题,有点庆幸没有用这个方案解决多线程并发读写的问题!
一种是多实例多线程模式,一种是单线程模式, 这个在使用多线程模式下也存在多线程访问安全的问题,所以使用了网上下面的配置:
sqlite3_open_v2(path, &db, SQLITE_OPEN_CREATE | SQLITE_OPEN_READWRITE | SQLITE_OPEN_FULLMUTEX, nil)
DBWrapper的封装:
@interface DBWraper ()
//用于对所有sql操作互斥
@property(nonatomic, strong) NSRecursiveLock *lock;
@end
@implementation DBWraper
+(instancetype) getInstance{
static DBWraper *_shareSingleton=nil;
static dispatch_once_t onceTokens;
dispatch_once(&onceTokens,^{
_shareSingleton=[[self alloc]init];
});
return _shareSingleton;
}
- (id)init{
self = [super init];
if (self){
self.lock = [[NSRecursiveLock alloc] init];
}
return self;
}
- (BOOL)lockCurrentThread:(NSThread *)currentThread currentFunc:(NSString*) currentFunc{
BOOL status = [self.lock tryLock];
if (!status){
LOG_ERROR(TAG_DB_MODULE, @"lockCurrentThread try lock failed! Lock thread:[%@] %@", currentThread, currentFunc);
if ([NSThread isMainThread]){
//lock失败情况下,直接放行,但不一定保证能读取到数据
return NO;
}
LOG_ERROR(TAG_DB_MODULE, @"lockCurrentThread not main thread, continue wait.");
//wait here
[self.lock lock];
}
return YES;
}
- (void)unlockCurrentThread{
[self.lock unlock];
if (DEBUG){
LOG_INFO(TAG_DB_MODULE, @"unlockCurrentThread success!");
}
}
+(void) dispatch_db_task:(dispatch_block_t) block{
dispatch_group_async([DBWraper getDBGroup], [DBWraper getDBQueue], block);
};
+(void) dispatch_db_notify_main:(dispatch_block_t) block{
dispatch_group_notify([DBWraper getDBGroup],dispatch_get_main_queue(),block);
}
+(dispatch_queue_t) getDBQueue{
static dispatch_queue_t dbQueue = nil;
static dispatch_once_t onceTokens;
dispatch_once(&onceTokens,^{
dbQueue = dispatch_queue_create( "db_update_queue", DISPATCH_QUEUE_SERIAL);//DISPATCH_QUEUE_CONCURRENT
});
return dbQueue;
}
+(dispatch_group_t) getDBGroup{
static dispatch_group_t group = nil;
static dispatch_once_t onceTokens;
dispatch_once(&onceTokens,^{
group = dispatch_group_create();
});
return group;
}
@end
业务模块:
[ DBWraper dispatch_db_task:^{
LOCK_DB_OPERATION
}];
[DBWraper dispatch_db_notify_main:^{
//通知更新UI
}];
使用LockGuard进行递归互斥锁保护单例对象的互斥方法:
//只需要new这个对象就可以保证如下操作被加锁,函数退出后自动解锁该对象
#define LOCK_DB_OPERATION LockGuard *lockGuard = [LockGuard new];
//用于封装线程递归互斥锁对象
@interface LockGuard:NSObject
@end
@interface LockGuard ()
@property(nonatomic, strong) NSString *lastCurrentlockFunc;
@property(nonatomic, strong) NSThread *lastCurrentlockThread;
@property(nonatomic, strong) NSString *currentlockFunc;
@property(nonatomic, strong) NSThread *currentlockThread;
@end
@implementation LockGuard
- (id)init{
self = [super init];
if (self){
NSArray *syms = [NSThread callStackSymbols];
if ([syms count] > 1) {
self.lastCurrentlockFunc = self.currentlockFunc;
self.lastCurrentlockThread = self.currentlockThread;
self.currentlockFunc = [syms objectAtIndex:1];
self.currentlockThread = [NSThread currentThread];
}
if (DEBUG){
LOG_INFO(TAG_DB_MODULE, @"lock - caller:%@ ", self.currentlockFunc);
}
BOOL value = [[DBWraper getInstance] lockCurrentThread:self.currentlockThread currentFunc:self.currentlockFunc];
if (!value){
LOG_ERROR(TAG_DB_MODULE, @"lock failed -last caller:%@,thread:%@ ", self.currentlockFunc, self.lastCurrentlockThread);
}
}
return self;
}
- (void)dealloc{
[[DBWraper getInstance] unlockCurrentThread];
if (DEBUG){
NSArray *syms = [NSThread callStackSymbols];
if ([syms count] > 1) {
LOG_INFO(TAG_DB_MODULE, @"unlock - caller:%@ ", self.currentlockFunc);
}
}
}
@end
图片来自:https://blog.csdn.net/a18339063397/article/details/90719433