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在其并发包中为我们 ...
随机推荐
- linux iptables扩展,脚本防火墙
netfileter:防火墙内核态ip tables:防火墙用户态(管理防火墙规则) iptables的表和链表包括不同的链,链包括大量的规则4个表: raw,mangle,nat,filter5种链 ...
- directdraw显示yuv422(yuy2)
#include <mmsystem.h> void CshowpicDlg::OnBnClickedButton3() { // TODO: 在此添加控件通知处理程序代码 height ...
- freemarker基本数据类型
freemarker基本数据类型 1.基本数据类型 (1)字符串 (2)数字 (3)布尔值 (4)日期 2.展示示例 <html> <head> <meta http-e ...
- 中断处理程序不能使用printf的本质
vxworks 中断处理程序之所以不用printf,本质在于printf是将信息输出到标准输出设备(STDOUT)中, 整个标准输出设备是一个全局变量,由于有semTake操作,那么就会发生阻塞,vx ...
- .Net4.0 任务(Task)
任务(Task)是一个管理并行工作单元的轻量级对象.它通过使用CLR的线程池来避免启动专用线程,可以更有效率的利用线程池.System.Threading.Tasks 命名空间下任务相关类一览: 类 ...
- 芝麻HTTP:TensorFlow LSTM MNIST分类
本节来介绍一下使用 RNN 的 LSTM 来做 MNIST 分类的方法,RNN 相比 CNN 来说,速度可能会慢,但可以节省更多的内存空间. 初始化 首先我们可以先初始化一些变量,如学习率.节点单元数 ...
- gplots heatmap.2和ggplot2 geom_tile实现数据聚类和热图plot
主要步骤 ggplot2 数据处理成矩阵形式,给行名列名 hclust聚类,改变矩阵行列顺序为聚类后的顺序 melt数据,处理成ggplot2能够直接处理的数据结构,并加上列名 ggplot_tile ...
- docker学习系列(一):docker 基础
一.简介 开发当中比较麻烦的问题在于软件配置,每个人的机械以及系统都不尽相同,程序需要运行在系统当中需要配置好相应的系统以及各种依赖的组件,但是很多时候由于各种软件依赖包的版本,系统兼容性问题会导致程 ...
- 【转载】Spark运行架构
1. Spark运行架构 1.1 术语定义 lApplication:Spark Application的概念和Hadoop MapReduce中的类似,指的是用户编写的Spark应用程序,包含了一个 ...
- js运算符单竖杠“|”的用法和作用及js数据处理
js运算符单竖杠“|”的作用 很多朋友都对双竖杠“||”,了如指掌,因为这个经常用到.但是大家知道单竖杠吗?今天有个网友QQ问我,我的 javascript实用技巧,js小知识 , 这篇文章里面,js ...