2009-07-21 6 views
2

Dans mon application, j'ai quelques threads qui exécutent une certaine logique. A la fin, ils ajoutent une nouvelle ligne à une table.Deux threads ajoutant de nouvelles lignes en même temps - comment prévenir?

Avant d'ajouter la nouvelle ligne, ils vérifient si une entrée précédente avec les mêmes détails n'existe pas déjà. Si l'on trouve - ils mettent à jour au lieu d'ajouter. Le problème est que quand un thread A fait la vérification, voir qu'aucune entité précédente avec les mêmes détails n'existe, et juste avant d'ajouter une nouvelle ligne, le thread B recherche le DB pour la même entité. Thread B voir qu'aucune telle entité n'existe donc il ajoute une nouvelle ligne aussi.

Le résultat est qu'il y a deux lignes avec les mêmes données dans la table.

Remarque: aucune clé de table n'est violée, car le thread obtient la séquence suivante juste avant d'ajouter la ligne et la clé de table est un ID qui n'est pas associé aux données.

Même si je vais modifier la clé de la table afin qu'elle soit une combinaison des données, elle empêchera deux lignes avec les mêmes données, mais provoquera une erreur de DB lorsque le deuxième thread essaiera d'ajouter la ligne.

Merci d'avance pour votre aide, Roy.

+1

Quelle langue utilisez-vous pour accéder à votre objet de données? – ChrisBD

Répondre

0

Vous devez envelopper les appels pour vérifier et écrire la ligne dans une section critique ou mutex.

Avec une section critique, les interruptions et la commutation de thread sont désactivées pendant que vous effectuez la vérification et l'écriture, de sorte que les deux threads ne peuvent pas écrire en même temps. Avec un mutex, le premier thread verrouillerait le mutex, effectuerait ses opérations, puis déverrouillerait le mutex. Le second thread tenterait de faire la même chose mais le verrou de mutex bloquerait jusqu'à ce que le premier thread libère le mutex.

Les implémentations spécifiques de la fonctionnalité de section critique ou de mutex dépendront de votre plate-forme.

4

Vous devez utiliser une file d'attente, en bloquant éventuellement la file d'attente. Les threads A et B (producteurs) ajoutent des objets à la file d'attente et un autre thread C (consommateur) interroge la file d'attente et supprime l'objet le plus ancien de la file d'attente en le conservant dans la base de données. Cela permettra d'éviter le problème quand A et B dans le même temps veulent conserver des objets égaux

2

Je pense que c'est un travail pour les contraintes SQL, à savoir "UNIQUE" sur l'ensemble des colonnes qui ont les données + l'erreur appropriée manipulation.

+0

Les contraintes arrêteront les entrées en double, mais vous pouvez vous assurer que vous n'obtenez pas le comportement ci-dessus en utilisant des transactions, n'est-ce pas? –

+0

En fait, oui. La réponse de finnw mise à jour. –

5

Vous parlez de "lignes" donc il s'agit probablement d'une base de données SQL?

Si oui, pourquoi ne pas simplement utiliser des transactions?

(À moins que les threads partagent une connexion de base de données, auquel cas un mutex pourrait aider, mais je préférerais donner à chaque fil une connexion séparée.)

-3

multi Threading est toujours bending ^^.

La principale chose à faire est de délimiter les ressources critiques et les opérations critiques.

  • Ressource critique: votre table.
  • opération critique: l'ajout oui, mais toute la procédure

Vous devez verrouiller l'accès à votre table dès le début de la vérification, jusqu'à la fin du module complémentaire. Si un thread tente de faire la même chose, alors qu'un autre ajoute/vérifie, il attend que le thread termine son opération. Aussi simple que cela.

+0

-1? Puis-je savoir pourquoi, alors je peux me corriger =) –

+0

Puis-je deviner que c'est l'utilisation de la langue? –

+1

J'espère que ce n'est pas le cas, ce serait l'un des plus sordides. -1 –

3

Je recommande d'éviter de bloquer dans la couche client. Synchronisé ne fonctionne que dans un processus, vous pouvez ensuite évoluer pour que vos threads se trouvent sur plusieurs JVM ou même sur des machines.

Je voudrais appliquer l'unicité dans la base de données, comme vous le suggérez, cela entraînera une exception pour le second inséreur. Attrapez cette exception et effectuez une mise à jour si c'est la logique métier dont vous avez besoin.

Mais considérez cet argument:

Parfois, l'une des séquences suivantes peuvent se produire:

A insérer des valeurs V A, mises à jour B aux valeurs V B.

B insérer V B, A mises à jour à V A.

Si les deux fils sont en compétition l'une de ces deux résultats V A ou V B est également valable. Donc, vous ne pouvez pas distinguer le deuxième cas de A insère V A et B échoue juste!

Donc en fait, le cas "échec et mise à jour" peut ne pas être nécessaire.

1

La plupart des frameworks de base de données (Hibernate dans Java, ActiveRecord etc dans Ruby) ont la forme optimistic locking. Cela signifie que vous exécutez chaque opération en supposant que cela fonctionnera sans conflit. Dans le cas spécial où il y a un conflit, vous vérifiez atomiquement au moment où vous effectuez l'opération de base de données, lancez une exception, ou code retour d'erreur, et réessayez l'opération dans votre code client après la requerying

est généralement implémenté en utilisant un numéro de version sur chaque enregistrement. Lorsqu'une opération de base de données est effectuée, la ligne est lue (y compris le numéro de version), le code client met à jour les données, puis la sauvegarde dans la base de données avec une clause where spécifiant l'ID de clé primaire ET le numéro de version était quand il a été lu. Si c'est différent - cela signifie qu'un autre processus a mis à jour la ligne, et l'opération doit être réessayée. Habituellement, cela signifie relire l'enregistrement, et refaire cette opération avec les nouvelles données de l'autre processus. Dans le cas de l'ajout, vous voudriez également un index unique sur la table, donc la base de données refuse l'opération, et vous pouvez gérer cela dans le même code.

Pseudo Code ressemblerait à quelque chose comme

do { 
    read row from database 
    if no row { 
    result_code = insert new row with data 
    } else { 
    result_code = update row with data 
    } 
} while result_code != conflict_code 

L'avantage est que vous n'avez pas besoin de synchronisation compliquée/verrouillage dans votre code client - chaque thread exécute seulement de manière isolée, et utilise la base de données comme contrôle de cohérence (ce qui est très rapide, et bon à).Comme vous ne verrouillez pas certaines ressources partagées pour chaque opération, le code peut s'exécuter beaucoup plus rapidement. Cela signifie également que vous pouvez exécuter plusieurs processus de système d'exploitation distincts pour répartir la charge et/ou mettre à l'échelle l'opération sur plusieurs serveurs sans aucun changement de code pour gérer les conflits.

0

Vous devez vérifier les lignes existantes, puis mettre à jour/ajouter des lignes dans une seule transaction. Lorsque vous effectuez votre vérification, vous devez également acquérir un verrou de mise à jour sur ces enregistrements, pour indiquer que vous allez écrire dans la base de données en fonction des informations que vous venez de lire, et que personne d'autre ne doit être autorisé pour le changer.

En pseudo T-SQL (pour Microsoft SQL Server):

BEGIN TRANSACTION 
SELECT id FROM MyTable WHERE SomeColumn = @SomeValue WITH UPDLOCK 
-- Perform your update here 
END TRANSACTION 

Le verrou de mise à jour empêche l'habitude des gens en train de lire ces documents, mais il empêchera les gens de l'écriture tout ce qui pourrait changer la sortie de votre SELECT

Questions connexes