我正在使用一个使用sqlite数据库的iPhone应用程序。该应用程序在后台线程中使用主线程中的UI下载Internet数据。后台下载线程可以在数据库上执行INSERT,UPDATE和SELECT。 UI层还可以通过执行UPDATE和SELECT与数据库交互。如果我在后台线程下载时没有与UI进行大量交互,一切正常。但是,在下载过程中执行主(UI)线程上的大量UPDATE时,我开始遇到问题。
应用程序在尝试运行数据库功能时总是退出。它退出EXC_BAD_ACCESS,我没有看到任何错误。例如,它最后一次退出时结束于sqlite3_step:
sqlite3_stmt *statement;
const char *query = "INSERT OR IGNORE INTO `names` (`id`,`name`) VALUES (?,?);";
if(sqlite3_prepare_v2(database, query, -1, &statement, NULL) != SQLITE_OK){
NSAssert1(0, @"Error while creating insert statement. '%s'", sqlite3_errmsg(database));
return NO;
}
sqlite3_bind_int(statement, 1, id);
sqlite3_bind_text(statement, 2, name, -1, SQLITE_TRANSIENT);
if(sqlite3_step(statement) != SQLITE_DONE)
NSAssert1(0, @"Error while inserting. '%s'", sqlite3_errmsg(database));
sqlite3_finalize(statement);
它并不总是退出sqlite3_step,有时会退出sqlite3_prepare_v2或sqlite3_exec。我已经尝试将这些语句放在一个循环中,如果它没有返回OK,请再试一次,但这也不起作用:
int returnCode = 0;
do{
returnCode = sqlite3_step(statement);
if(returnCode != SQLITE_DONE){
usleep(20);
}
}while(returnCode != SQLITE_DONE);
我也尝试过SQL事务,但这没有任何区别。我怎么解决这个问题?这似乎是一个相当基本的并发问题,但我没有看到任何对我有用的东西。
感谢您的帮助, 贾斯汀
答案 0 :(得分:2)
除非您使用特殊设置重新编译它,否则SQLite不是线程安全的。
请参阅http://www.sqlite.org/faq.html#q6
因此,您需要注意访问数据库并从同一个线程调用它上面的SQL操作。
然而,我提出了一个解决方案,即使在多线程环境中也似乎没问题:我确保任何SQLite操作都受@synchronized
指令保护,以确保一旦线程正在执行在DB上的某些东西,任何其他线程都无法访问它。
因此,我宁愿说“确保在不同的线程中并行执行两个操作”,而不是说“所有SQlite操作都应该在同一个线程中完成”。
答案 1 :(得分:1)
我正在使用Objective-C编写一个几乎相同的w.r.t行为的程序。
这是我打算如何同步访问(我问的问题有点无关,但看看代码):
Calling sqlite3_close for a static sqlite3* handle
我将使用静态NSLock实例并在写入时锁定它,然后在我完成时将其解锁。
我不知道您的应用程序会有多大的变化,但它可能是一个解决方案。
答案 2 :(得分:1)
我不确定这是否是一个有效的解决方案,但我要做的是在一个单独的线程中下载所有数据。但是当下载完成后,返回主线程,并在主线程中进行插入。
dispatch_async(dispatch_get_global_queue(0, 0), ^{
//download data from internet
dispatch_async(dispatch_get_main_queue(), ^{
//update database here
}
}
这样您就不会遇到任何可能的多线程问题。由于下载是花费最多的时间,所以它在另一个线程中完成,但是数据库的更新不应该花费那么长时间......所以它只会占用主线程几乎无法察觉的时间段。至少它应该是如果查询不是很慢,并且它们不是很多。
答案 3 :(得分:0)
我在我的应用程序中遇到了同样的问题。每当从互联网更新数据的线程开始写入数据库的同时我进行了一些触发数据库访问的UI交互,程序就崩溃了。
我的DB-handler中的每个数据库查询的@synchronized语句似乎都解决了这个问题。
答案 4 :(得分:0)
从版本3.5.0开始,您可以在多个线程之间共享相同的数据库连接:http://www.sqlite.org/34to35.html检查您正在使用的SQLite版本。
另请查看sqlite3_threadsafe函数。
我编写了一个C ++程序,它在两个线程之间共享数据库连接,并且没有得到seg错误(我相信这与EXC_BAD_ACCESS相同):https://gist.github.com/allyourcode/7428159该示例显示了内存数据库的使用,但是我在磁盘支持的数据库中获得了类似的结果。
我想用数据竞赛工具(如tsan)对此进行分析,但我需要弄清楚如何做到这一点:P