2010-06-29 66 views
6

我正在使用FMDB处理我的数据库,它工作正常。该应用程序使用后台线程正在做一些工作,需要访问数据库。同时主线程需要在同一个数据库上运行一些查询。 FMDB本身有一个小小的锁定系统,但是,我在课堂上添加了另一个锁定系统。当在iOS 4.0上使用SQLite(FMDB)和线程时EXC_BAD_ACCESS

只有当我的类指示数据库未被使用时才会执行每个查询。执行操作后,数据库将被解锁。只要负载不太高,它就可以按预期工作。当我使用主线程上运行的线程访问大量数据时,会出现EXC_BAD_ACCESS错误。

这里是寻找:

- (BOOL)isDatabaseLocked { 
    return isDatabaseLocked; 
} 

- (Pile *)lockDatabase { 
    isDatabaseLocked = YES; 
    return self;   
} 

- (FMDatabase *)lockedDatabase { 
    @synchronized(self) { 
     while ([self isDatabaseLocked]) { 
      usleep(20); 
      //NSLog(@"Waiting until database gets unlocked..."); 
     } 
     isDatabaseLocked = YES; 
     return self.database;  
    } 
} 

- (Pile *)unlockDatabase { 
    isDatabaseLocked = NO; 
    return self;    
} 

调试器说,在[FMResultSet next]出现在该行

rc = sqlite3_step(statement.statement); 

我仔细检查了所有的保留计数和所有的对象都在这个时候存在误差。同样,它只发生在主线程在后台线程运行时启动大量查询(这本身总是产生大量负载)。错误总是由主线程产生,而不是由后台线程产生。

我最后的想法是,这两个线程在同一时间运行lockedDatabase这样他们就可以得到一个数据库对象。这就是为什么我通过“@synchronized(self)”添加了互斥锁。但是,这并没有帮助。

有没有人有线索?

+0

这线程的FMDB问题给出了一些其他有用的见解可能的原因:https://github.com/ccgus/fmdb/issues/39 – 2015-04-17 19:01:13

回答

2

您应该添加同步的包装围绕你的功能unlockDatabase和lockDatabase,以及isDatabaseLocked - 它并不总是保证一个变量的存储或检索是原子的。当然,如果你这样做,你会想把你的睡眠移动到同步块之外,否则你会死锁。这本质上是一个自旋锁 - 它不是最有效的方法。

- (FMDatabase *)lockedDatabase { 
    do 
    { 
     @synchronized(self) { 
      if (![self isDatabaseLocked]) { 
       isDatabaseLocked = YES; 
       return self.database; 
      } 
     } 
     usleep(20);  
    }while(true); // continue until we get a lock 
} 

你确保你不使用FMDatabase目标对象的所谓unlockDatabase后?您可能需要考虑句柄模式 - 创建一个包装FMDatabase对象的对象,并且只要它存在就对数据库持有锁定。在init中声明该锁,并在dealloc中,您可以释放该锁。那么你的客户端代码不需要担心调用各种锁定/解锁功能,而且你不会意外地搞砸了。尝试使用NSMutex而不是@synchronized块,请参阅http://developer.apple.com/mac/library/documentation/Cocoa/Conceptual/Multithreading/ThreadSafety/ThreadSafety.html#//apple_ref/doc/uid/10000057i-CH8-SW16

+0

我发现了一段代码,我直接访问数据库。所以它没有被锁定。解决这个小问题后,一切都很好。 – danielkbx 2010-06-30 12:40:36

+0

你是如何解决这个问题的?你可以发布有帮助的代码吗? – Split 2011-06-22 21:21:15

+0

我使用我最初发布的代码。我的错误是,该应用程序不锁定数据库查询,因此没有调用lockedDatabase。 – danielkbx 2012-02-15 08:47:11

0

您也可以尝试FMDatabaseQueue - 我专门为此类情况创建了它。我还没有尝试过,但我相当肯定它会为iOS 4,工作

+0

我认为FMDatabaseQueue仅适用于查询,是吗?有没有一种简单的方法来做同样的更新(除了上面的SQLite设置)? – 2012-07-29 17:39:34

6

的SQLite提供了一个更简单的序列化。通过设置sqlite_config()选项SQLITE_CONFIG_SERIALIZED,您可能会避免大部分这些类型的麻烦。在与线程问题长期斗争后,我发现了这种困难。

这里是你如何使用它,你可以把它放在FMDatabase的init方法...

if (sqlite3_config(SQLITE_CONFIG_SERIALIZED) == SQLITE_ERROR) { 
     NSLog(@"couldn't set serialized mode"); 
    } 

查看threadsafetyserialized mode SQLite的文档的更多信息。

+1

谢谢!谢谢你,谢谢你,谢谢你。这正是我需要的! – DOOManiac 2012-11-17 03:02:30

+0

SQLite文档说它默认为序列化模式,但它看起来像MacOS附带的库的版本被设置为默认为单线程模式(我发现困难的方式,同时移植完美的程序在Linux和Windows上到Mac)。 – 2013-02-09 15:45:58

+0

伙计们,你能为我解释一件事吗? SQLITE_CONFIG_SERIALIZED保证在sqlite里打开互斥体,很酷。所以它的所有方法都是原子的,对吧?但是谁能保证在并发队列中的客户端方法中一切都正确? 对于,例如我们所称 无效美孚(){ sqlite3_open() sqlite3_exec() sqlite3_next_stmt() sqlite3_finalize() sqlite3_close() } 方法内的每个呼叫是原子和安全的内部。但是当我们从不同的线程调用'foo'时,方法会被混淆。就像从线程2'打开'之后从线程1'完成'一样,等等。 – 2017-01-25 15:43:48

0

我遇到了这个问题,仅仅通过打开准备好的语句的缓存就能够消除这个问题。

FMDatabase *myDatabase = [FMDatabase databaseWithPath: pathToDatabase]; 
myDatabase.shouldCacheStatements = YES;