2010-06-29 11 views
6

Estoy usando FMDB para tratar con mi base de datos que funciona bien. La aplicación utiliza un hilo de fondo que está trabajando y necesita acceder a la base de datos. Al mismo tiempo, el hilo principal necesita ejecutar algunas consultas en la misma base de datos. El propio FMDB tiene un pequeño sistema de bloqueo, sin embargo, agregué otro a mis clases.EXC_BAD_ACCESS al usar SQLite (FMDB) y subprocesos en iOS 4.0

Cada consulta solo se realiza si mi clase indica que la base de datos no está en uso. Después de realizar las acciones, la base de datos se desbloquea. Esto funciona como se espera siempre que la carga no sea demasiado alta. Cuando accedo a una gran cantidad de datos con el subproceso que se ejecuta en el subproceso principal, se produce un error EXC_BAD_ACCESS.

Aquí está el futuro:

- (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;    
} 

El depurador dice que el error se produce en [FMResultSet next] en la línea de

rc = sqlite3_step(statement.statement); 

Me doble comprobado todos conservan existen recuentos y todos los objetos en este momento. De nuevo, solo ocurre cuando el hilo principal inicia muchas consultas mientras se ejecuta el hilo de fondo (que a su vez produce una gran carga). El error siempre es producido por el hilo principal, nunca por el hilo de fondo.

Mi última idea es que ambos hilos ejecutan lockedDatabase al mismo tiempo para que puedan obtener un objeto de base de datos. Es por eso que agregué el bloqueo mutex a través de "@synchronized (self)". Sin embargo, esto no ayudó.

¿Alguien tiene una pista?

+0

Este hilo para un problema FMDB da alguna otra información útil sobre las posibles causas: https://github.com/ccgus/fmdb/issues/39 –

Respuesta

2

Debe agregar el contenedor sincronizado alrededor de sus funciones unlockDatabase y lockDatabase, así como isDatabaseLocked - no siempre se garantiza que una tienda o la recuperación de una variable sea atómica. Por supuesto, si lo hace, querrá mover su suspensión fuera del bloque sincronizado, de lo contrario se estancará. Esto es esencialmente un bloqueo de giro, no es el método más eficiente.

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

¿Se asegura de que usted no utiliza el objeto FMDatabase después de haber llamado unlockDatabase? Es posible que desee considerar un patrón de manejo: cree un objeto que ajuste el objeto FMDatabase y, mientras exista, mantenga un bloqueo en la base de datos. En init reclamas el bloqueo, y en dealloc, puedes liberar ese bloqueo. Entonces su código de cliente no necesita preocuparse por llamar a las diversas funciones de bloqueo/desbloqueo, y no se equivocará accidentalmente. Intente utilizar NSMutex en lugar de los bloques @synchronized, consulte http://developer.apple.com/mac/library/documentation/Cocoa/Conceptual/Multithreading/ThreadSafety/ThreadSafety.html#//apple_ref/doc/uid/10000057i-CH8-SW16

+0

encontré una pieza de código donde puedo acceder a la base de datos directamente. Entonces no se bloqueó. Después de solucionar este pequeño problema, todo funciona perfectamente bien. – danielkbx

+0

¿Cómo resolvió este problema? ¿Puedes publicar el código que ayudó? – Split

+0

Uso el código que originalmente publiqué. Mi error fue que la aplicación realizó una consulta de base de datos sin bloquear, por lo que no llamó a lockedDatabase. – danielkbx

0

También puede probar FMDatabaseQueue - Lo he creado específicamente para situaciones como esta. No lo he probado, pero estoy bastante seguro de que funcionará para iOS 4.

+0

Creo que FMDatabaseQueue funciona solo para consultas, ¿verdad? ¿Existe alguna manera directa de hacer lo mismo con las actualizaciones (aparte de la configuración de SQLite anterior)? –

6

SQLite proporciona una serialización mucho más simple. Con solo establecer la opción SQLITE_CONFIG_SERIALIZED de sqlite_config() probablemente evitará la mayoría de estos tipos de dolores de cabeza. Descubrí esto de la manera difícil después de luchar con problemas de enhebrado durante mucho tiempo.

Así es como usted lo utiliza, se puede poner en el método init de FMDatabase ...

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

Consulte la documentación de SQLite en threadsafety y serialized mode para obtener más información.

+1

¡GRACIAS! Gracias, gracias, gracias. Esto es exactamente lo que necesitaba! – DOOManiac

+0

La documentación de SQLite dice que está por defecto en modo serializado, pero parece que la versión de las bibliotecas que vienen con MacOS está configurada de manera predeterminada en modo de una sola cadena (lo que descubrí por las malas, mientras que portaba un programa que funcionaba perfectamente en Linux y Windows a la Mac). –

+0

Chicos, ¿pueden explicarme una cosa? SQLITE_CONFIG_SERIALIZED garantiza la activación de material mutex dentro de sqlite, genial. Entonces todos sus métodos son atómicos, ¿verdad? Pero, ¿quién puede garantizar que todo vaya bien en los métodos del cliente en una cola simultánea? Para , ejemplo nos llaman void foo() { sqlite3_open() sqlite3_exec() sqlite3_next_stmt() sqlite3_finalize() sqlite3_close() } Cada llamada dentro del método es atómico y seguro en el interior. Pero cuando llamamos 'foo' desde diferentes hilos, los métodos se llaman caóticamente. Como 'finalizar' desde el hilo 1 después de 'abrir' desde el hilo 2 y así sucesivamente. –

0

Estaba teniendo este problema y pude eliminar el problema simplemente activando el almacenamiento en caché de las declaraciones preparadas.

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