这篇文章原来在用 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 如何保证线程安全的更多相关文章

  1. EF 保证线程内唯一 上下文的创建

    1.ef添加完这个对象,就会自动返回这个对象数据库的内容,比如下面这个表是自增ID 最后打印出来的ID  就是自增的结果 2.lambda 中怎么select * var userInfoList = ...

  2. 多线程下C#如何保证线程安全?

    多线程编程相对于单线程会出现一个特有的问题,就是线程安全的问题.所谓的线程安全,就是如果你的代码所在的进程中有多个线程在同时运行,而这些线程可能会同时运行这段代码.如果每次运行结果和单线程运行的结果是 ...

  3. 在JAVA中ArrayList如何保证线程安全

    [b]保证线程安全的三种方法:[/b]不要跨线程访问共享变量使共享变量是final类型的将共享变量的操作加上同步一开始就将类设计成线程安全的, 比在后期重新修复它,更容易.编写多线程程序, 首先保证它 ...

  4. java中volatile不能保证线程安全

    今天打了打代码研究了一下java的volatile关键字到底能不能保证线程安全,经过实践,volatile是不能保证线程安全的,它只是保证了数据的可见性,不会再缓存,每个线程都是从主存中读到的数据,而 ...

  5. 最近面试被问到一个问题,AtomicInteger如何保证线程安全?

    最近面试被问到一个问题,AtomicInteger如何保证线程安全?我查阅了资料 发现还可以引申到 乐观锁/悲观锁的概念,觉得值得一记. 众所周知,JDK提供了AtomicInteger保证对数字的操 ...

  6. OSSpinLockLock加锁机制,保证线程安全并且性能高

    在aspect_add.aspect_remove方法里面用了aspect_performLocked, 而aspect_performLocked方法用了OSSpinLockLock加锁机制,保证线 ...

  7. Qunar机票技术部就有一个全年很关键的一个指标:搜索缓存命中率,当时已经做到了>99.7%。再往后,每提高0.1%,优化难度成指数级增长了。哪怕是千分之一,也直接影响用户体验,影响每天上万张机票的销售额。 在高并发场景下,提供了保证线程安全的对象、方法。比如经典的ConcurrentHashMap,它比起HashMap,有更小粒度的锁,并发读写性能更好。线程安全的StringBuilder取代S

    Qunar机票技术部就有一个全年很关键的一个指标:搜索缓存命中率,当时已经做到了>99.7%.再往后,每提高0.1%,优化难度成指数级增长了.哪怕是千分之一,也直接影响用户体验,影响每天上万张机 ...

  8. (C#) 多线程访问探讨,如果保证线程安全?

    先抛出几点疑问: 1. 多个线程同时访问同一个“值类型变量“(value type, stored in stack), 如果保证安全访问? 2. 多个线程同时访问同一个“引用类型变量“(refere ...

  9. ConcurrentHashMap如何保证线程安全

    以前看过HashMap的内部实现,知道HashMap是使用Node数组+链表+红黑树的数据结构来实现,如下图所示.但是HashMap是非线程安全,在多线程环境不能够使用. 不过JDK在其并发包中为我们 ...

随机推荐

  1. java网络编程(3)——UDP

    UDP在java中主要使用DatagramSocket来实现通讯,数据一般是通过DatagramPacket来封装: 发送方只需指定接受方的地址和端口,然后通过send()方法就可以把封装在Datag ...

  2. CentOS6实现路由器功能

    网络之间的通信主要是依靠路由器,当然生成环境中是拥有路由器的,但是系统中的路由配置也是需要了解一下地,今天讲解一下在CentOS6环境下搭建路由器,此乃入门级的简单实验.拓扑如上图已经规划好,暂且使用 ...

  3. 转 Caffe学习系列(4):激活层(Activiation Layers)及参数

    在激活层中,对输入数据进行激活操作(实际上就是一种函数变换),是逐元素进行运算的.从bottom得到一个blob数据输入,运算后,从top输入一个blob数据.在运算过程中,没有改变数据的大小,即输入 ...

  4. qwe 简易深度框架

    qwe github地址 简介 简单的深度框架,参考Ng的深度学习课程作业,使用了keras的API设计. 方便了解网络具体实现,避免深陷于成熟框架的细节和一些晦涩的优化代码. 网络层实现了Dense ...

  5. R︱sparkR的安装与使用、函数尝试笔记、一些案例

    本节内容转载于博客: wa2003 spark是一个我迟早要攻克的内容呀~ ------------------------------------- 一.SparkR 1.4.0 的安装及使用 1. ...

  6. SOCKET 编程TCP/IP、UDP

    TCP/IP 资源:http://download.csdn.net/detail/mao0514/9061265 server: #include<stdio.h> #include&l ...

  7. java中final和static

    final的意思是最终的,最后的额,不可变的,在java中也具有相似的含义. final修饰基础数据表示把该数据修饰成常量,意味着不可修改,不可变. final修饰对象的引用的时候,表示该引用不可变, ...

  8. SecurityError:Error:#2148

    1.错误描述 SecurityError:Error:#2148:SWF文件http://localhost:8888/UploadDownload/Flash/ReadLocalFile.swf/[ ...

  9. MySQL语法大全整理的自学笔记

    select * from emp; #注释 #--------------------------- #----命令行连接MySql--------- #启动mysql服务器 net start m ...

  10. httpclient的主要业务代码逻辑(图解)

    一,主要代码逻辑(图解) 二,两个案例的对比(图解) 三,详细案例 3.1,博文一 httppost的用法(NameValuePair(简单名称值对节点类型)核心对象) 3.2,博文二 httpcli ...