FMDatabaseQueue 如何保证线程安全
这篇文章原来在用 Github Pages 搭建的博客上,现在决定重新用回博客园,所以把文章搬回来。
FMDB 是 OC 针对 sqlite 的封装。在其文档的线程安全部分这样讲:同时从多个线程使用同一个FMDatabase的实例是一个糟糕的想法。在单个线程中使用FMDatabase没有问题,但是不要在线程间共享一个FMDatabase的对象。如果你不听劝阻,那么坏的事情将接踵而至,比如应用崩溃或者异常,也有可能有陨石从天上掉下来砸向你的 Mac Pro(还好买不起垃圾桶)。这相当糟糕。
Using a single instance of FMDatabase from multiple threads at once is a bad idea. It has always been OK to make a FMDatabase object per thread. Just don't share a single instance across threads, and definitely not across multiple threads at the same time. Bad things will eventually happen and you'll eventually get something to crash, or maybe get an exception, or maybe meteorites will fall out of the sky and hit your Mac Pro. This would suck.
同时,文档也给出了线程安全的方法,即使用FMDatabaseQueue。这篇文章就来分析 FMDatabaseQueue是如何做到线程安全的。
从初始化说起
+ (instancetype)databaseQueueWithPath:(NSString*)aPath;
+ (instancetype)databaseQueueWithPath:(NSString*)aPath flags:(int)openFlags;
- (instancetype)initWithPath:(NSString*)aPath;
- (instancetype)initWithPath:(NSString*)aPath flags:(int)openFlags;
FMDatabaseQueue这四个初始化方法,其最终都会调用到initWithPath:flags:方法。其实现如下:
- (instancetype)initWithPath:(NSString*)aPath flags:(int)openFlags {
self = [super init];
if (self != nil) {
_db = [[[self class] databaseClass] databaseWithPath:aPath];
FMDBRetain(_db);
#if SQLITE_VERSION_NUMBER >= 3005000
BOOL success = [_db openWithFlags:openFlags];
#else
BOOL success = [_db open];
#endif
if (!success) {
NSLog(@"Could not create database queue for path %@", aPath);
FMDBRelease(self);
return 0x00;
}
_path = FMDBReturnRetained(aPath);
_queue = dispatch_queue_create([[NSString stringWithFormat:@"fmdb.%@", self] UTF8String], NULL);
dispatch_queue_set_specific(_queue, kDispatchQueueSpecificKey, (__bridge void *)self, NULL);
_openFlags = openFlags;
}
return self;
}
首先为实例化FMDatabase的一个实例_db,然后打开数据库。生成一个串行队列,然后调用dispatch_queue_set_specific为生成的 queue 设置关联的上下文数据。
这里需要注意两点,一是_db和_path都是FMDatabaseQueue的数据成员,二是为生成的串行队列关联的上下文数据是self,即FMDatabaseQueue本身。
inDatabase:
- (void)inDatabase:(void (^)(FMDatabase *db))block {
/* Get the currently executing queue (which should probably be nil, but in theory could be another DB queue
* and then check it against self to make sure we're not about to deadlock. */
FMDatabaseQueue *currentSyncQueue = (__bridge id)dispatch_get_specific(kDispatchQueueSpecificKey);
assert(currentSyncQueue != self && "inDatabase: was called reentrantly on the same queue, which would lead to a deadlock");
FMDBRetain(self);
dispatch_sync(_queue, ^() {
FMDatabase *db = [self database];
block(db);
if ([db hasOpenResultSets]) {
NSLog(@"Warning: there is at least one open result set around after performing [FMDatabaseQueue inDatabase:]");
#if defined(DEBUG) && DEBUG
NSSet *openSetCopy = FMDBReturnAutoreleased([[db valueForKey:@"_openResultSets"] copy]);
for (NSValue *rsInWrappedInATastyValueMeal in openSetCopy) {
FMResultSet *rs = (FMResultSet *)[rsInWrappedInATastyValueMeal pointerValue];
NSLog(@"query: '%@'", [rs query]);
}
#endif
}
});
FMDBRelease(self);
}
开始的两行稍后讨论,我们往下看。
使用dispatch_sync同步派发到_queue,然后通过[self database]获取FMDatabase对象来执行对数据库的操作。自己可以看一下[self database]方法,实际上还是获取了初始化方法中的_db对象。这样就确保了唯一的FMDatabase对象在一个串行队列_queue中执行,而且是 sync。
FMDatabaseQueue中其他的方法,思路与此一致,不再讨论。
处理潜在的死锁问题
这里有一个问题,如果调用dispatch_sync的队列与其派发的队列是同一个队列,而且都是串行队列。那么会发生什么?没错,死锁。
我们在初始化函数中把FMDatabaseQueue的成员_queue初始化为了一个串行队列,那么如果调用inDatabase方法的队列跟_queue是同一个队列,就会造成死锁。开始的两行就是来处理这个问题。
当然,这发生的几率很小,一是初始化方法中dispatch_queue_create的第一个参数(用于指定队列的标签)是这样的[[NSString stringWithFormat:@"fmdb.%@", self] UTF8String],二是dispatch_queue_set_specific的key是这样的static const void * const kDispatchQueueSpecificKey = &kDispatchQueueSpecificKey;。如果你不故意要撞车,这个问题发生的概率要比中五百万的概率低得多。
FMDatabaseQueue 如何保证线程安全的更多相关文章
- EF 保证线程内唯一 上下文的创建
1.ef添加完这个对象,就会自动返回这个对象数据库的内容,比如下面这个表是自增ID 最后打印出来的ID 就是自增的结果 2.lambda 中怎么select * var userInfoList = ...
- 多线程下C#如何保证线程安全?
多线程编程相对于单线程会出现一个特有的问题,就是线程安全的问题.所谓的线程安全,就是如果你的代码所在的进程中有多个线程在同时运行,而这些线程可能会同时运行这段代码.如果每次运行结果和单线程运行的结果是 ...
- 在JAVA中ArrayList如何保证线程安全
[b]保证线程安全的三种方法:[/b]不要跨线程访问共享变量使共享变量是final类型的将共享变量的操作加上同步一开始就将类设计成线程安全的, 比在后期重新修复它,更容易.编写多线程程序, 首先保证它 ...
- java中volatile不能保证线程安全
今天打了打代码研究了一下java的volatile关键字到底能不能保证线程安全,经过实践,volatile是不能保证线程安全的,它只是保证了数据的可见性,不会再缓存,每个线程都是从主存中读到的数据,而 ...
- 最近面试被问到一个问题,AtomicInteger如何保证线程安全?
最近面试被问到一个问题,AtomicInteger如何保证线程安全?我查阅了资料 发现还可以引申到 乐观锁/悲观锁的概念,觉得值得一记. 众所周知,JDK提供了AtomicInteger保证对数字的操 ...
- OSSpinLockLock加锁机制,保证线程安全并且性能高
在aspect_add.aspect_remove方法里面用了aspect_performLocked, 而aspect_performLocked方法用了OSSpinLockLock加锁机制,保证线 ...
- Qunar机票技术部就有一个全年很关键的一个指标:搜索缓存命中率,当时已经做到了>99.7%。再往后,每提高0.1%,优化难度成指数级增长了。哪怕是千分之一,也直接影响用户体验,影响每天上万张机票的销售额。 在高并发场景下,提供了保证线程安全的对象、方法。比如经典的ConcurrentHashMap,它比起HashMap,有更小粒度的锁,并发读写性能更好。线程安全的StringBuilder取代S
Qunar机票技术部就有一个全年很关键的一个指标:搜索缓存命中率,当时已经做到了>99.7%.再往后,每提高0.1%,优化难度成指数级增长了.哪怕是千分之一,也直接影响用户体验,影响每天上万张机 ...
- (C#) 多线程访问探讨,如果保证线程安全?
先抛出几点疑问: 1. 多个线程同时访问同一个“值类型变量“(value type, stored in stack), 如果保证安全访问? 2. 多个线程同时访问同一个“引用类型变量“(refere ...
- ConcurrentHashMap如何保证线程安全
以前看过HashMap的内部实现,知道HashMap是使用Node数组+链表+红黑树的数据结构来实现,如下图所示.但是HashMap是非线程安全,在多线程环境不能够使用. 不过JDK在其并发包中为我们 ...
随机推荐
- Hive分区和桶
SMB 存在的目的主要是为了解决大表与大表间的 Join 问题,分桶其实就是把大表化成了“小表”,然后 Map-Side Join 解决之,这是典型的分而治之的思想.在聊 SMB Join 之前,我们 ...
- 1_Two Sum --LeetCode
原题如下: 思路:将nums放到一个map<int,int>中,其中,键是nums中元素,值对应其下标.然后遍历nums,取nums中一个值nums[i],接着用target减去它,最后再 ...
- SQL Server使用侦听器IP访问时遇到"The target principal name is incorrect. Cannot generate SSPI context"
在测试SQL Server 2016 Always On时,在创建侦听器后,在客户端使用SSMS, 可以用侦听器名称访问Always On集群,但是使用侦听器IP访问时遇到"The targ ...
- Android Gradle项目Hotfix热修复技术的接入
https://github.com/AItsuki/HotFix Issues MAC系统无法自动打包补丁,原因可能是路径分隔符问题 使用谷歌multidex分包后无法注入代码(开启multidex ...
- 笔记+R︱信用风险建模中神经网络激活函数与感知器简述
每每以为攀得众山小,可.每每又切实来到起点,大牛们,缓缓脚步来俺笔记葩分享一下吧,please~ --------------------------- 本笔记源于CDA-DSC课程,由常国珍老师主讲 ...
- JavaScript实现弹窗报错
JavaScript实现弹窗报错 1.具体错误如下 SCRIPT 5022:cannot call methods on dialog prior to initialization; attempt ...
- JavaScript控制输入框中只能输入中文、数字和英文
1.问题背景 遇到这样一个问题:有一个输入框,要求只能输入中文.数字和英文(即过滤特殊字符串) 2.JS代码 function checkUsername() { //正则表达式 var reg = ...
- CF368 D - Persistent Bookcase
re了20多发 还是我在测试数据上操作最后了10多发才发现的 其实只需要多加一句就好了 真的愚蠢啊,要不都能进前100了 #include<bits/stdc++.h> using nam ...
- span是没有value标签的,要向获得标签内部的值改怎么办。
1,js实现 var div = document.getElementById('divId');var spans = div.getElementsByTagName('span');var s ...
- 【BZOJ2684】【CEOI2004】锯木厂选址(斜率优化,动态规划)
[BZOJ2684][CEOI2004]锯木厂选址(斜率优化,动态规划) 题面 万恶的BZOJ因为权限题的原因而做不了... 我要良心的提供题面 Description 从山顶上到山底下沿着一条直线种 ...