我有一个使用sqlite 3.6(不带FMDB)的iPhone应用程序来存储和加载数据。我在应用程序加载时加载数据库,并通过整个应用程序使用相同的数据库连接。
在后台线程中,应用程序从Web服务器下载一些数据并写入数据库。同时主线程也可能需要写入同一个数据库。这有时会导致EXC_BAD_ACCESS,因为两个线程都试图访问数据库。
能够从不同线程使用数据库的最佳和最简单的方法是什么?
这是一个显示问题的示例:
sqlite3 *database;
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
NSString *path = [documentsDirectory stringByAppendingPathComponent:@"database.db"];
if (sqlite3_open([path UTF8String], &database) != SQLITE_OK) {
sqlite3_close(database);
return YES;
}
[NSThread detachNewThreadSelector:@selector(test) toTarget:self withObject:nil];
[self test];
return YES;
}
-(void)test {
for (int i = 0; i < 2000; i++) {
NSLog(@"%i",i);
sqlite3_exec([self getDb],"UPDATE mytable SET test=''", 0, 0, 0);
}
}
修改
在下面的willcodejavaforfood的回答之后,我试图改变我的代码,为每个单独的线程使用一个单独的数据库对象(连接),并且还添加了sqlite3_busy_timeout(),以便在数据库繁忙时sqlite将重试写入。现在我不再获得EXC_BAD_ACCESS,但我注意到并非所有数据都被插入。所以这也不是一个稳定的解决方案。似乎很难让sqlite使用线程..
我的新解决方案具有单独的连接:
-(void)test {
sqlite3 *db = [self getNewDb];
for (int i = 0; i < 2000; i++) {
NSLog(@"%i",i);
sqlite3_exec(db,"UPDATE mytable SET test=''", 0, 0, 0);
}
}
- (sqlite3 *)getNewDb {
sqlite3 *newDb = nil;
if (sqlite3_open([[self getDbPath] UTF8String], &newDb) == SQLITE_OK) {
sqlite3_busy_timeout(newDb, 1000);
} else {
sqlite3_close(newDb);
}
return newDb;
}
答案 0 :(得分:3)
我通过使用一个线程和一个NSOperationQueue
来插入数据来解决这个问题。我会考虑一下。我从来没有能够获得一个具有多线程的稳定系统,并且大多数写入并不重要,排队确实有帮助。
根据要求,还有一些信息:
我有一个NSOperation
的子类,我用我想要存储的模型对象进行实例化。
这些操作不是提交给在单独线程中运行的NSOperationsQueue的扩展。此自定义队列只是添加指向数据库实例的指针。执行操作时,它使用[NSOperationsQueue currentQueue]
属性访问队列而不是数据库。故意,我使用非并发操作(maxOperations设置为1)
因此,只有一个查询(或更新)在一个时间连续执行,完全在后台执行。
显然你完成后需要某种回调。
这可能不是我能找到的快速,但最稳定,最干净的解决方案。
<强>文档:强>
http://developer.apple.com/library/ios/documentation/General/Conceptual/ConcurrencyProgrammingGuide/OperationObjects/OperationObjects.html
http://www.cimgf.com/2008/02/16/cocoa-tutorial-nsoperation-and-nsoperationqueue/
http://icodeblog.com/2010/03/04/iphone-coding-turbo-charging-your-apps-with-nsoperation/
答案 1 :(得分:2)
正如您所注意到的,一次只有一个线程可以访问sqlite数据库。防止同时访问的选项:
前三个选项可能导致忙等待(一个线程等待另一个线程释放锁定),这是浪费。
我使用选项4,因为它简化了创建新查询以在后台运行并且没有繁忙等待的任务。它还确保所有查询按照添加的顺序执行(我的代码倾向于假设)。
dispatch_queue_t _queue = dispatch_queue_create("com.mycompany.myqueue", DISPATCH_QUEUE_SERIAL);
// Run a query in the background.
dispatch_async(_queue, ^{
...some query
// Perhaps call a completion block on the main thread when done?
dispatch_async(dispatch_get_main_queue(), ^{
//completion(results, error);
});
});
// Run a query and wait for the result.
// This will block until all previous queries have finished.
// Note that you shouldn't do this in production code but it may
// be useful to retrofit old (blocking) code.
__block NSArray *results;
dispatch_sync(_queue, ^{
results = ...
});
...use the results
dispatch_release(_queue);
在一个完美的世界中,sqlite可以让你同时执行读操作,但一次只能执行一次写操作(例如,使用dispatch_barrier_async()进行写操作,使用dispatch_async()进行读取)。
答案 2 :(得分:1)
在并发部分的C ore Data Programming Guide中对此进行了解释。
建议并发使用的模式 使用Core Data进行编程是线程 约束
你应该给每个线程都有自己的 完全私人管理对象 上下文并保持相关联 对象图分离 每线程基础。
有两种可能的采用方法 模式:
创建单独的托管对象 每个线程的上下文并共享一个 单个持久性存储协调器。 这是通常推荐的 方法
创建单独的托管对象 上下文和持久存储 每个线程的协调员。这个 方法规定更大 并发性以牺牲更大 复杂性(特别是如果你需要 沟通之间的变化 不同的背景)和增加 内存使用情况。
答案 3 :(得分:1)
我尝试过这两种解决方案,但效果很好。您可以使用关键部分或NSOperationQueue,我更喜欢第一个,这里是两个代码:
定义一些类“DatabaseController”并将此代码添加到其实现中:
static NSString * DatabaseLock = nil;
+ (void)initialize {
[super initialize];
DatabaseLock = [[NSString alloc] initWithString:@"Database-Lock"];
}
+ (NSString *)databaseLock {
return DatabaseLock;
}
- (void)writeToDatabase1 {
@synchronized ([DatabaseController databaseLock]) {
// Code that writes to an sqlite3 database goes here...
}
}
- (void)writeToDatabase2 {
@synchronized ([DatabaseController databaseLock]) {
// Code that writes to an sqlite3 database goes here...
}
}
要使用NSOperationQueue,您可以使用:
static NSOperationQueue * DatabaseQueue = nil;
+ (void)initialize {
[super initialize];
DatabaseQueue = [[NSOperationQueue alloc] init];
[DatabaseQueue setMaxConcurrentOperationCount:1];
}
+ (NSOperationQueue *)databaseQueue {
return DatabaseQueue;
}
- (void)writeToDatabase {
NSInvocationOperation * operation = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(FUNCTION_THAT_WRITES_TO_DATABASE) object:nil];
[operation setQueuePriority:NSOperationQueuePriorityHigh];
[[DatabaseController databaseQueue] addOperations:[NSArray arrayWithObject:operation] waitUntilFinished:YES];
[operation release];
}
这两个解决方案会阻塞当前线程,直到完成对数据库的写入,在大多数情况下您可以考虑这些。