7
votes

Exc_bad_access lors de l'utilisation de SQLite (FMDB) et des threads sur iOS 4.0

J'utilise FMDB pour faire face à ma base de données qui fonctionne bien. L'application utilise un fil d'arrière-plan qui fait du travail et doit accéder à la base de données. Dans le même temps, le fil principal doit exécuter certaines requêtes sur la même base de données. FMDB elle-même a un petit système de verrouillage, cependant, j'ai ajouté une autre à mes classes.

Chaque requête n'est effectuée que si ma classe indique que la base de données n'est pas utilisée. 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'accumule beaucoup de données avec le fil exécutant sur le thread principal, une erreur exc_bad_access se produit.

Voici la recherche: xxx

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

i vérifié tous les comptes de retenue et tous les objets existent à ce moment-là. Encore une fois, il ne se produit que lorsque le thread principal commence beaucoup de requêtes alors que le fil d'arrière-plan est en marche (qui produisent toujours une charge lourde). L'erreur est toujours produite par le thread principal, jamais par le fil d'arrière-plan.

Ma dernière idée serait que les deux threads exécutent verrouillés en même temps afin de pouvoir 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é.

Est-ce que quelqu'un a un indice?


1 commentaires

Ce fil pour un problème de la FMDB donne une autre perspective utile sur les causes éventuelles: Github.com/ccgus/fmdb / Problèmes / 39


4 Réponses :


2
votes

Vous devez ajouter le wrapper synchronisé autour de vos fonctions déverrindatabase et Lockdatabase, ainsi que isdatabaselocked - il n'est pas toujours garanti qu'un magasin ou une récupération d'une variable est atomique. Bien sûr, si vous le faites, vous voudrez vous déplacer votre sommeil en dehors du bloc synchronisé, sinon vous ferez une impasse. C'est essentiellement une serrure 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
}


3 commentaires

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


Comment avez-vous corrigé ce problème? Pouvez-vous poster le code qui a aidé?


J'utilise le code que j'ai publié à l'origine. Mon erreur était que l'application a fait une interrogation de base de données sans verrouillage sans appeler LockedDatabase.



0
votes

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


1 commentaires

Je pense que fmdatabasequeue ne fonctionne que pour les requêtes, n'est-ce pas? Y a-t-il un moyen simple de faire la même chose pour les mises à jour (à part le paramètre SQLite ci-dessus)?



6
votes

SQLite fournit une sérialisation beaucoup plus simple. En définissant simplement l'option SQLITE_CONFIG () SQLITE_CONFIG_SERIALISÉE, vous éviterez probablement la plupart de ces types de maux de tête. J'ai découvert cela de manière difficile après avoir combattu avec des problèmes de filetage pendant un long moment.

Voici comment vous l'utilisez, vous pouvez le mettre dans la méthode init de fmdatabase ... xxx

Voir les docs SQLite sur threadsafety et Mode Serialized pour plus d'informations.


4 commentaires

JE VOUS REMERCIE! Merci merci merci. C'est exactement ce dont j'avais besoin!


La documentation SQLITE indique qu'il est par défaut en mode Serialized, mais il semble que la version des bibliothèques fournie avec les macos est définie sur le mode de sauvegarde par défaut au mode mono-thread (que j'ai découvert de manière difficile, tout en portant un programme qui fonctionnait parfaitement sur Linux et Windows au Mac).


Les gars, pouvez-vous expliquer pour moi une chose? SQLITE_CONFIG_SERIALISÉ GARANTIES Allumer des trucs mutex à l'intérieur de la SQLite, cool. Donc, toutes ses méthodes sont atomiques, non? Mais qui peut garantir que tout va bien dans les méthodes des clients dans une file d'attente simultanée? Pour, Exemple Nous appelons Void Foo () {SQLITE3_OPEN () SQLITE3_EXEC () SQLITE3_EXEC () SQLITE3_NEXTE_STMT () SQLITE3_FORMIZE () SQLITE3_FLOSE ()} Chaque appel à l'intérieur de la méthode est atomique et sûr à l'intérieur. Mais lorsque nous appelons le 'FOO' de différentes méthodes de threads sont appelés chaotiquement. Comme "finaliser" du fil 1 après "ouvert" du fil 2 et ainsi de suite.


Oui, SQLite3_Open renvoie différentes instances à chaque fois. Mais en réalité, j'ai 100% de cas lorsque notre application se bloque si je supprimais @Synchronisés à partir d'une méthode qui utilise SQLite. Et oui, il peut être sans rapport avec SQLite et je vais davantage d'enquêter sur ce problème.



0
votes

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

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


0 commentaires