Accès concurrent à  une base de donnée locale

Bonjour,



Je suis actuellement en train de rencontrer un problème sur mon application. Depuis plusieurs threads, l'appli accède à  une base de donnée locale afin d'effectuer des enregistrement.



Le problème est qu'il semble impossible de permettre à  la base de donnée d'avoir plusieurs accès simultanés.



Je pensais utiliser un objet qui gérerait un thread obligatoire pour l'accès à  la BD, avec du coup une file d'attente, mais je n'ai aucune expérience dans ce domaine.



Quelqu'un aurait-il rencontré ce problème?
Mots clés:

Réponses

  • CéroceCéroce Membre, Modérateur
    C'est quoi comme BdD ?
  • AliGatorAliGator Membre, Modérateur
    à‰videmment si c'est du SQLite c'est pas fait pour les accès multithreading (bien que libsqlite3 propose quelques options dans ce domaine mais cela n'empêche qu'il est bien dit que SQLite est une base mono-utilisateur pas dimensionnée pour les accès concurrentiels comme peuvent l'être des SGBD serveur type MySQL)



    Le plus simple c'est d'utiliser NSOperation pour empiler tes requêtes sur une NSOperationQueue, ou d'utiliser GCD et empiler tes requêtes sur une queue serial. Il te restera quand même quelques subtilités à  gérer mais l'idée est là 



    Comme d'habitude pour tous les problèmes de multithreading je déconseille fortement d'utiliser NSThread, tout comme Apple le déconseille également, il y a tellement de subtilités à  gérer avec les API de threading bas niveau de ce type et c'est devenu obsolète avec toutes les nouvelles méthodes pour faire du concurrency programming bien plus efficace que ça serait bête de ce passer de ces nouvelles technos. Je t'invite très très fortement à  lire le Concurrency Programming Guide dans la doc Apple qui explique les diverses techniques pour migrer des NSThread vers les nouvelles technos, pourquoi c'est mieux et moins compliqué, et tous les concepts à  connaà®tre et à  avoir conscience quand on fait du multithreading. Il y a également plusieurs vidéos WWDC sur le sujet qui valent le coup d'être regardées, car le multithreading à  bon nombre de subtilités.



    Pour ma part pour ma base de données interne de cache j'ai fait un service dédié dans mon appli qui utilise les avantages de GCD pour avoir une file d'attente (type queue GCD avec une toute bête dispatch_queue_t) en postant mes requêtes dessus (dispatch_async) et j'utilise les blocks à  fond pour me signaler quand une requête à  fini de s'exécuter, par exemple pour me retourner une réponse.

    Comme ça tout mon système d'interaction avec ma BDD est thread-safe, asynchrone (non bloquant pour l'UI), et permet d'avoir du code appelant qui reste lisible comme si c'était synchrone (vive les completionBlocks) plutôt que de devoir mettre des delegate partout ou quoi.
  • MistifiouMistifiou Membre
    novembre 2012 modifié #4
    Alors j'ai trouvé une piste, qui est effectivement d'utiliser les queue. J'utilise
    dispatch_synch
    
    pour le moment et cela semble marcher mais j'aimerais pouvoir utiliser une
    dispatch_queue_t
    
    spéciale pour les enregistrements en BD ...



    Je viens de finir la fin de ton message Ali, je vois qu'on est sur la même longueur d'ondes image/tongue.png' class='bbc_emoticon' alt=':P' />. Alors la question clef est : comment j'e réserve une queue juste pour la base de données? je n'ai pas trouvé de
    dispatch_create_queue
    
    ou une méthode du genre.





    Edit : j'ai trouvé, pour informations, créer une dispatch_queue_t se fait avec
    <br />
    dispatch_queue_create(&quot;com.toto.tata&quot;, NULL)<br />
    


    Si je ne me suis pas trompé image/smile.png' class='bbc_emoticon' alt=':)' />



    Merci a vous !
  • AliGatorAliGator Membre, Modérateur
    Oui c'est ça



    Je t'invite à  lire le Programming Guide sur GCD qui explique les différents types de queue, les différents concepts de GCD en détail, et comment faire certaines opérations usuelles. Tu y apprendras par exemple pourquoi dans ton cas utiliser dispatch_sync n'est pas une bonne idée (ça va bloquer ton code du main thread jusqu'à  l'exécution de ta requête sur ta queue dédiée, rendant le tout synchrone, si tu commences à  avoir des requetes qui prennent du temps ou s'entassent dans ta queue ça va geler ton appli jusqu'à  leur exécution) et comment faire pour rendre ça asynchrone.



    Je te conseille également à  nouveau les sessions vidéo WWDC dédiées à  GCD qui sont plus qu'instructives comme souvent.
  • Bonjour,

    FMDB pourrait peut-être répondre assez facilement à  tes attentes.
  • Je comprend pour le sync, le soucis est qu'il me faut une valeur de retour et si j'exécute le block avec asynch je ne vais pas pouvoir retourner de valeur ... a moins que je n'ai pas vraiment compris la différence entre les deux.



    @Lexxis : j'utilise déjà  FMDB. En fait je possède sur l'appli un singleton héritant d'un FMDatabase (je m'y suis peut-être mal pris, mais je trouve cette méthode d'accès plus propre). Le soucis est qu'il fallait que j'accède à  ce singleton depuis le même thread, sinon certaines requêtes tentaient de s'effectuer en même temps ce qui générait des erreurs.
  • Tu utilises la classe FBDatabaseQueue ? Regarde la partie 'Using FMDatabase and Thread Safety'.
  • AliGatorAliGator Membre, Modérateur
    novembre 2012 modifié #9
    'Mistifiou' a écrit:


    Je comprend pour le sync, le soucis est qu'il me faut une valeur de retour et si j'exécute le block avec asynch je ne vais pas pouvoir retourner de valeur ... a moins que je n'ai pas vraiment compris la différence entre les deux.
    C'est pour ça que les blocs c'est magique.



    Tu peux te faire des méthodes qui fonctionnent un peu de la même manière que toutes ces APIs d'Apple utilisant des "completionBlocks". Donc au lieu de retourner immédiatement une valeur de retour (ce que tu ne peux pas faire conceptuellement puisque tu dois prévoir une API asynchrone), tu appelles un block quand tu as le résultat pour exécuter le reste du code.

    C'est plus compliqué à  expliquer qu'à  faire et à  utiliser, vraiment. Et en plus ça te permet de retourner plusieurs valeurs à  la fois.



    Par exemple si dans ta base tu as une liste de personnes associés à  des IDs et que tu veux une méthode que te retourne le nom et le prénom de la personne correspondant à  un ID donné, ça donnera :
    typedef void (^PersonCompletion)(NSString* firstname, NSString* lastname);<br />
    <br />
    -(void)fetchNamesForPersonWithID:(int)personID completion:(PersonCompletion)completion<br />
    {<br />
       dispatch_queue_t callerQueue = dispatch_queue_get_current();<br />
    <br />
       // Exécuter ta requête sur ta dbQueue dédiée<br />
       dispatch_async(dbQueue, ^{<br />
          // Tes appels à  SQLite, sqlite3_step(...), etc<br />
          NSString* firstname = [NSString stringWithCString: sqlite3_column_text(...) encoding:...];<br />
          NSString* lastname = [NSString stringWithCString: sqlite3_column_text(...) encoding:...];<br />
    <br />
          // Et là  pour retourner ta valeur de façon asynchrone, plutôt qu&#39;un return, tu appelles le completionBlock, tout simplement.<br />
          // Il faut quand même appeler ce block de completion sur la queue qui a appelé notre méthode (en général la main queue) et pas sur la dbQueue<br />
          dispatch_async(callerQueue, ^{<br />
             completion(firstname, lastname);<br />
          });<br />
       });<br />
    }
    
    Voilà , en fait à  peu de choses près cela revient à  remplacer ta méthode qui retourne un objet par une méthode qui ne retourne rien (void) et prend un completionBlock en paramètre... et dans le code de ta méthode, de remplacer le "return" par un appel au completionBlock ! En prenant juste soin de l'appeler sur la queue d'origine et pas sur la dbQueue, mais c'est à  peu près tout ce que ça change sinon image/smile.png' class='bbc_emoticon' alt=':)' />
Connectez-vous ou Inscrivez-vous pour répondre.