2010-03-15 5 views
1

J'utilise le code BlockingQueue posté in this question, mais j'ai réalisé que j'avais besoin d'utiliser une pile au lieu d'une file d'attente étant donné le fonctionnement de mon programme. Je l'ai converti pour utiliser une pile et renommé la classe au besoin. Pour les performances, j'ai supprimé le verrouillage dans Push, car mon code producteur est monothread.Comment faire Stack.Pop threadsafe

Mon problème est de savoir comment le thread travaillant sur le Stack (maintenant) thread sûr sait quand il est vide. Même si j'ajoute un autre wrapper thread sûr autour de Count qui se verrouille sur la collection sous-jacente comme Push et Pop, je suis toujours dans la condition de course que l'accès Count, puis Pop ne sont pas atomiques.

solutions possibles que je les vois (qui est préféré et suis-je manque tout qui fonctionnerait mieux?):

  1. fils de consommateurs attraper la InvalidOperationException lancée par Pop(). Pop() renvoie un nullptr lorsque _stack-> Count == 0, mais C++ - CLI n'a pas l'opérateur par défaut() ala C#. Pop() renvoie une valeur booléenne et utilise un paramètre de sortie pour renvoyer l'élément sauté.

Voici le code que je utilise en ce moment:

generic <typename T> 
public ref class ThreadSafeStack 
{ 
public: 
    ThreadSafeStack() 
    { 
    _stack = gcnew Collections::Generic::Stack<T>(); 
    } 

public: 
    void Push(T element) 
    { 
    _stack->Push(element); 
    } 

    T Pop(void) 
    { 
    System::Threading::Monitor::Enter(_stack); 
    try { 
     return _stack->Pop(); 
    } 
    finally { 
     System::Threading::Monitor::Exit(_stack); 
    } 
    } 

public: 
    property int Count { 
    int get(void) 
    { 
     System::Threading::Monitor::Enter(_stack); 
     try { 
     return _stack->Count; 
     } 
     finally { 
     System::Threading::Monitor::Exit(_stack); 
     } 
    } 
    } 

private: 
    Collections::Generic::Stack<T> ^_stack; 
}; 
+0

"Pour les performances, j'ai supprimé le verrouillage dans Push, car mon code de producteur est à filetage unique." Qu'entendez-vous par là? Vous avez seulement un producteur? Votre code de producteur fonctionne-t-il en même temps que les consommateurs? – tony

+0

Oui, je n'ai qu'un seul producteur et aucun code de producteur ne fonctionne en même temps que les consommateurs. Il fonctionne d'abord, puis exécute les multiples consommateurs avec le ThreadSafeStack produit par le producteur. –

Répondre

4

Personnellement, j'utiliser votre option 3., mais renommer ce TryPop(). Cela lui permettrait de se comporter davantage comme le ConcurrentQueue<T>.TryDequeue du Framework (dans .NET 4).


Edit:

Je déclare comme ceci:

public: 
bool TryPop([Out] T% result); 

Dans votre implémentation, vous définissez simplement la valeur T dans votre corps de méthode ...

+0

Vous ne savez pas du tout comment utiliser les paramètres de C++ -cli, des liens ou des exemples de code? –

+0

@ user260197: Je viens d'ajouter la déclaration pour vous ... –

+0

Étant donné que le TryPop devrait retourner un nullptr si la pile est vide, recommandez-vous de passer dans un nullptr où j'appelle TryPop ou de m'assurer que j'ai défini le paramètre out sur nullptr? Encore une fois, je ne suis pas sûr de savoir comment faire l'équivalent C# par défaut (T). J'ai trouvé ceci: http: // stackoverflow.com/questions/1962600/quoi-est-le-c-cli-équivalent-à-cs-defaultt –

2

Option # 3 est le chemin à parcourir et Marc Gravell a posté une excellente mise en œuvre d'un BufferedQueue/BlockingQueue qu'il a appelé SizeQueue:

Creating a blocking Queue<T> in .NET?

Étant donné l'exemple de la file d'attente de Marc, il devrait être assez facile d'échanger dans une pile et il fonctionnerait de façon similaire.

+0

Merci, ceci est un excellent lien. –