2009-08-19 7 views
2

J'ai une application de console filetée qui fonctionne bien, mais son architecture doit être améliorée, et j'aimerais recevoir des commentaires.Verrouillage C# et question de conception de filetage général

Actuellement, le programme charge une liste de données et segmente ces données en partitions (un morceau pour chaque thread). Le programme initialise ensuite un nouveau thread à l'aide de ThreadPool et lui transmet UN segment des données partitionnées sur lequel opérer.

Tout fonctionne bien ... sauf:

Certains des fils échouent ... en raison de problèmes de réseau, ou des exceptions irrécupérables. C'est un comportement attendu et pas un bug.

J'ai maintenant besoin d'un moyen (si le thread échoue) de récupérer le segment de données de ce thread et de le fournir à un autre thread de travail afin qu'il ne devienne pas orphelin. Je suis sûr qu'il existe des moyens de le faire, c'est-à-dire de partager des données entre les threads, etc., mais je pense qu'il y a une meilleure approche. Au lieu de segmenter les données au préalable et de les passer à chaque thread, je pouvais partager une collection statique de ces données entre tous les threads. C'est plus élégant, mais introduit de nouveaux problèmes de synchronisation que l'ancienne approche n'avait pas à s'inquiéter. A.) Que pensez-vous de cette approche par rapport à l'ancienne?
B.) Si cette approche est bonne, comment puis-je verrouiller l'accès à la collection statique partagée.

Lorsque le thread est initialisé, je peux verrouiller la collection et enlever un segment des données juste pour ce thread. La collection statique serait maintenant REDUITE par la quantité sautée pour ce fil. Après ÉCHEC du thread, je pourrais réaffecter ce segment de données à la collection partagée en le verrouillant à nouveau et en repoussant les données dans la collection pour que d'autres threads tentent de les traiter.

Par exemple: (non testé pseudocode)

void Process(object threadInfo) 
{ 
    lock(StaticCollection) 
    { 
    var segment = StaticCollection.Take(100); 
    StaticCollection.Remove(StaticCollection.Where(item => segment.Contains(item))) 
    } 

    foreach(var seg in segment) 
    { 
    // do something 
    } 

    // reallocate the thread's data on failure 
    if(unrecoverableErrorOccurred) 
    { 
    lock(StaticCollection) 
    { 
     StaticCollection.Add(segment); 
    } 
    } 
} 

Am I sur la bonne voie avec cela? Il me semble qu'un thread peut supprimer des éléments en même temps qu'un autre thread réalloue des éléments ... ou un verrou sur une collection STATIC signifie qu'aucun autre thread ne peut accéder à cette collection. Donc, Thread A.) a obtenu un verrou dans la PREMIERE partie de la méthode, cela bloquerait-il tous les autres threads d'exécuter la dernière partie de la méthode jusqu'à ce que ThreadA soit complet?

+0

Votre solution est réellement très agréable (à part les appels Take et Remove, vous pouvez envisager d'utiliser une pile réelle). J'aime la solution que vous fournissez car elle vous permettrait également de repousser uniquement les objets qui ont échoué dans la pile ou la file d'attente, et ne nécessite pas d'autre collection pour suivre l'état de traitement de chaque élément. Maintenant, quelqu'un va probablement crier à propos de pourquoi c'est horrible ... – LorenVS

+0

L'opération doit-elle réussir ou échouer en tant que transaction unique? (Et le processus modifie-t-il les données dans le segment sur lequel il opère?) –

+0

Non sur les deux points Jeff. – Scott

Répondre

4

Séparons quelques petites choses ici ...

Tout d'abord, vous n'êtes pas fait verrouillage de la collection. Vous verrouillez le moniteur associé à un objet. Je pense personnellement que c'était une erreur que .NET ait suivi Java en donnant à chaque objet un moniteur associé à verrouiller, mais laissons cela de côté. Personnellement, je préfère avoir des objets et des variables associées uniquement pour le verrouillage - donc dans mon code que vous pouvez voir:

private readonly object padlock = new object(); 

Cela garantit que aucun autre code va essayer d'acquérir ce verrou, parce qu'ils ont gagné Je ne connais pas l'objet.

Deuxièmement, les verrous sont consultatifs. Cela fait partie de l'activité de "vous ne verrouillez pas la collection."Si la collection elle-même se synchronise sur le même verrou - et les collections non génériques ont une méthode Synchronized dans ce but précis - mais à moins que quelque chose n'exclue explicitement un verrou, vous n'obtiendrez pas de synchronisation. oui, les deux blocs verrouillés montrés dans votre code sont en utilisant le même verrou (en supposant que la valeur de StaticCollection ne change pas, bien sûr.) Si un thread est occupé en appelant Remove, cela empêchera tout autre thread d'appeler Add à en même temps, parce qu'ils ont chacun besoin d'avoir le verrou.C'est probablement ce que vous voulez

Personnellement, je ne le ferais pas vraiment statique collection si (ou plutôt, je ne voudrais pas utiliser une variable StaticCollection). Je donnerais à chaque tâche une référence à la même collection (et une référence à la serrure associée, en fait j'enchaînerais probablement la collection, la synchronisation et le "fais-moi un tas de travail" et "voici un tas de travail pour avoir des "bits de retour" dans une classe séparée). Cela rendra plus facile à tester et généralement plus agréable logiquement. Cela signifie également que vous pourriez avoir deux "ensembles" distincts de threads travaillant sur différentes collections en même temps ... ce qui pourrait être utile si vous faites l'encapsulation générique ci-dessus, donc ils pourraient effectuer des tâches radicalement différentes ...

+0

Merci beaucoup pour les idées. – Scott

0

Vous pouvez utiliser une file d'attente pour stocker des blocs non traités et, comme le dit Jon Skeet, verrouiller un objet neutre et maintenir le verrou seulement assez longtemps pour accéder à la file d'attente. J'ai utilisé cette approche avec beaucoup de fils et cela a bien fonctionné pour moi.

Questions connexes