2010-06-29 13 views
6

J'utilise FMDB pour traiter ma base de données qui fonctionne très bien. L'application utilise un thread d'arrière-plan qui effectue un travail et doit accéder à la base de données. En même temps, le thread principal doit exécuter certaines requêtes sur la même base de données. FMDB lui-même a un petit système de verrouillage, cependant, j'en ai ajouté un autre à mes cours.EXC_BAD_ACCESS lors de l'utilisation SQLite (FMDB) et les discussions sur iOS 4.0

Chaque requête est effectuée uniquement si ma classe indique que la base de données n'est pas utilisé. Après avoir effectué les actions, la base de données est déverrouillée. Cela fonctionne comme prévu tant que la charge n'est pas trop élevée. Lorsque j'accède à beaucoup de données avec le thread s'exécutant sur le thread principal, une erreur EXC_BAD_ACCESS se produit.

Voici la recherche:

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

Le débogueur dit que l'erreur se produit à [FMResultSet next] à la ligne

rc = sqlite3_step(statement.statement); 

Je double vérifié tous les conserver compte et tous les objets existent à ce moment. Encore une fois, cela ne se produit que lorsque le thread principal démarre beaucoup de requêtes pendant que le thread d'arrière-plan est en cours d'exécution (qui lui-même produit toujours une charge importante). L'erreur est toujours produite par le thread principal, jamais par le thread d'arrière-plan.

Ma dernière idée serait que les deux fils courent lockedDatabase en même temps afin qu'ils puissent obtenir un objet de base de données. C'est pourquoi j'ai ajouté le verrouillage mutex via "@synchronized (self)". Cependant, cela n'a pas aidé.

Quelqu'un at-il une idée?

+0

Ce fil pour une question FMDB donne un autre aperçu utile des causes possibles: https://github.com/ccgus/fmdb/issues/39 –

Répondre

2

Vous devez ajouter l'encapsuleur synchronisé autour de vos fonctions unlockDatabase et lockDatabase, ainsi que isDatabaseLocked - il n'est pas toujours garanti qu'un stockage ou une récupération d'une variable est atomique. Bien sûr, si vous le faites, vous voudrez déplacer votre sommeil hors du bloc synchronisé, sinon vous serez bloqué. C'est essentiellement un verrou de spin - ce n'est pas la méthode la plus efficace.

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

vous assurez-vous que vous n'utilisez pas l'objet FMDatabase après avoir appelé unlockDatabase? Vous pouvez envisager un modèle de poignée - créer un objet qui enveloppe l'objet FMDatabase et, tant qu'il existe, détient un verrou sur la base de données. Dans init vous revendiquez le verrou, et dans dealloc, vous pouvez libérer ce verrou. Ensuite, votre code client n'a pas à s'inquiéter de l'appel des différentes fonctions de verrouillage/déverrouillage, et vous ne risquez pas de bousiller accidentellement. Essayez d'utiliser NSMutex au lieu des blocs @synchronized, voir http://developer.apple.com/mac/library/documentation/Cocoa/Conceptual/Multithreading/ThreadSafety/ThreadSafety.html#//apple_ref/doc/uid/10000057i-CH8-SW16

+0

J'ai trouvé un morceau de code où j'accéder directement à la base de données. Donc ça n'a pas été verrouillé. Après avoir réglé ce petit problème, tout fonctionne parfaitement bien. – danielkbx

+0

Comment avez-vous résolu ce problème? Pouvez-vous poster le code qui a aidé? – Split

+0

J'utilise le code que j'ai posté à l'origine. Mon erreur a été que l'application a fait une requête de base de données sans verrouillage, donc sans appeler lockedDatabase. – danielkbx

0

Vous pouvez également essayer FMDatabaseQueue - Je l'ai créé spécifiquement pour des situations comme celle-ci. Je ne l'ai pas essayé, mais je suis assez sûr que ça va fonctionner pour iOS 4.

+0

Je pense que FMDatabaseQueue ne fonctionne que pour les requêtes, n'est-ce pas? Existe-t-il un moyen simple de faire la même chose pour les mises à jour (à part le réglage SQLite ci-dessus)? –

6

SQLite offre une sérialisation beaucoup plus simple. En réglant simplement l'option sqlite_config() SQLITE_CONFIG_SERIALIZED vous probablement éviter la plupart de ces types de maux de tête. J'ai découvert cela à la dure après avoir lutté avec des problèmes de threading pendant longtemps.

Voilà comment vous l'utilisez, vous pouvez le mettre dans la méthode init de FMDatabase ...

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

Voir les docs sur SQLite threadsafety et serialized mode pour plus d'informations.

+1

MERCI! Merci merci merci. C'est exactement ce dont j'avais besoin! – DOOManiac

+0

La documentation de SQLite indique qu'elle utilise par défaut le mode sérialisé, mais il semble que la version des bibliothèques fournies avec MacOS soit configurée par défaut en mode mono-thread (que j'ai découvert à la dure, en portant un programme qui fonctionne parfaitement sur Linux et Windows sur Mac). –

+0

Les gars, pouvez-vous expliquer une chose pour moi? SQLITE_CONFIG_SERIALIZED garantit l'activation de mutex à l'intérieur du sqlite, cool. Donc toutes ses méthodes sont atomiques, n'est-ce pas? Mais qui peut garantir que tout va bien dans les méthodes client dans une file d'attente concurrente? Pour, par exemple, nous appelons void foo() { sqlite3_open() sqlite3_exec() sqlite3_next_stmt() sqlite3_finalize() sqlite3_close() } Chaque appel à l'intérieur de la méthode est à l'intérieur atomique et en toute sécurité. Mais quand nous appelons le 'foo' de différents threads, les méthodes sont appelées chaotiquement. Comme 'finaliser' du fil 1 après 'ouvert' du fil 2 et ainsi de suite. –

0

J'avais ce problème et a pu éliminer le problème simplement en activant la mise en cache des déclarations préparées.

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