2010-02-26 8 views
2

Mon (simplifié) Le tableau se compose d'unMarquer un dossier comme « utilisé » pour soutenir multithreading

Id int identity(1,1), 
File varchar(20), 
FileProcessed bit 

La logique est la suivante: mon application prend d'abord (l'ordre n'a pas d'importance) dossier, qui a Bit FileProcessed défini sur false. Ensuite, il traite le fichier et définit le bit FileProcessed sur true.

Maintenant, il peut arriver que le premier thread prenne un enregistrement avec Id 1 et en le traitant, un autre thread prend le même enregistrement avec Id 1 (car il n'est pas market comme traité).

Quelle est la meilleure façon, pour soutenir miltithreading dans cet exemple?

EDIT: J'utilise SQL Sever 2005 EDIT2: Traitement du fichier peut prendre beaucoup de temps, donc je ne veux pas verrouiller la table tout en attendant

Répondre

3

D'autres ont mentionné l'ajout d'une colonne supplémentaire - vous pouvez également envisager de changer la colonne FileProcessed pour qu'elle soit une colonne appelée par ex. état - où vous pourriez modéliser Non Traités, Traités, Traités, Faultés? (par exemple, que se passe-t-il si le fichier ne peut pas être traité). En outre, si le traitement échoue, vous souhaitez réessayer de traiter le fichier immédiatement. Si le processeur meurt de façon inattendue, comment allez-vous gérer cela (par exemple, vous pouvez vouloir une autre table qui décrit quand chaque tentative de traitement commence - et si la dernière tentative a commencé il y a 20 minutes (ou tout ce qui semble raisonnable) une tentative ratée.

Pour faire la sélection/mise à jour correctement, vous voudrez peut-être un script comme le suivant:

declare @FileID int 

BEGIN TRANSACTION 

select top 1 @FileID = FileID from FilesToDoStuffTo with (updlock,holdlock,readpast) where Status=Unprocessed 

update FilesToDoStuffTo set Status = Processing where FileID = @FileID 

COMMIT 

faire alors tout ce que vous devez faire avec le @FileID que vous avez sélectionné.

+0

+1 Meilleure réponse jusqu'à présent. Recommande de stocker un identifiant unique de l'instance effectuant le traitement sur la ligne. Quand les choses tournent mal (et c'est le cas, bien sûr), il peut être très utile de savoir * quelle * instance était censée traiter cette ligne, peut-être de la déverrouiller parce que l'instance est allée à pied. :-) –

+0

@OP: J'ai transféré mon commentaire à une réponse comme demandé, mais j'utiliserais vraiment l'approche de Damien où vous le pouvez. :-) –

+0

peut vouloir utiliser aussi READPAST pour que d'autres puissent traiter la file d'attente aussi ... –

2

Vous devez envelopper votre logique d'application dans TransactionScope sections. De cette façon, chaque appel à la base de données est dans une transaction de lui-même.

Pour être certain qu'un appel est effectivement verrouillé, utilisez un constructeur qui prend un TransactionScopeOption, plus précisément l'option Required, afin que les transactions soient toujours en place.

Cela peut avoir un impact sur les performances, vous devrez donc le tester. Vous ne pouvez pas compter uniquement sur la modification d'un champ dans la base de données, car vous risquez d'obtenir des lectures incorrectes (un thread indique que l'enregistrement n'est pas "utilisé", un autre le marque "en cours d'utilisation" et le premier essaie toujours de travailler avec). Par conséquent, une combinaison de prise en charge des transactions et d'un champ dans la base de données fonctionnera le mieux.

0

Ajoutez un nouveau drapeau appelé traitement, ou jst marquez l'enregistrement tel que traité lorsque le premier thread le récupère, en fonction de vos besoins. Notez également que lorsque vous tirez l'enregistrement et que vous le marquez comme étant traité, vous devez verrouiller la table, car vous pouvez lire l'enregistrement, puis avant de le marquer comme traité/traité, un autre thread pourrait entrer et prends-le aussi.

1

Modifier la structure de table:

Id int identity(1,1), 
File varchar(20), 
FileOnProcess bit, 
FileProcessed bit 

Maintenant, vous pouvez simplement verrouiller la ligne et mettre à jour le bit FileOnProcess afin que vous sélectionnez uniquement les fichiers qui sont pas déjà processus. En fonction de votre moteur de base de données, la commande SQL à verrouiller peut différer.

+0

Plutôt qu'un simple drapeau, je recommande fortement d'utiliser une colonne dans laquelle un identifiant unique de l'instance traitant la ligne peut être stocké. Lorsque les choses tournent mal (et elles le feront), il est utile de savoir quels enregistrements ont été traités par une instance qui s'est promenée. –

1

(Republier commentaire comme une réponse, tel que demandé par OP.)

Je pense que Damien_The_Unbeliever's answer est la meilleure approche générale lors de l'utilisation d'un SGBDR (tel que SQL Server) qui prend en charge, sauf (comme je l'ai mentionné dans mon commentaire à lui), j'inclurais une colonne identifiant l'instance traitant la ligne. Si le SGBDR ou l'environnement que vous utilisez rend difficile ce qui précède, et si vous attribuez à chaque instance son propre ID unique, vous pouvez obtenir un effet similaire en ayant une colonne normalement NULL que vous pouvez définir pour le traitement de l'instance la ligne. Puis (même sans les transactions) que vous pouvez faire

set rowcount 1 
update FilesToDoStuffTo 
set BeingProcessedBy = {theid} 
where FileProcessed = 0 and BeingProcessedBy is NULL 

... puis

select FileID from FilesToDoStuffTo where BeingProcessedBy = {theid} 

(En général, votre infrastructure de connexion DB - ODBC, JDBC, quel que soit - a une enveloppe pour le set rowcount 1; essentiellement , vous voulez être sûr de ne mettre à jour une ligne, pas tous! Ou saisir par lots de 5, 10, quel que soit logique pour ce que vous faites.)

Mieux vaut éviter ce genre de jeu si vous le pouvez (en utilisant transactio ns et/ou des procédures stockées pour au moins sélectionner la rangée, les verrous de rangée, etc.), mais parfois une approche standard de tourbière est la plus pratique. :-)

Questions connexes