2017-06-15 4 views
1

J'essaie de résoudre le problème du consommateur producteur en C++ 11. J'ai un objet qui contient des ressources, et plusieurs threads peuvent ajouter ou consommer ces ressources. Mon problème est quand j'essaie d'implémenter une méthode "consommer quand disponible" sur cet objet. Veuillez supposer que les opérations d'insertion/suppression sont d'une complexité triviale.Comment résoudre correctement le consommateur producteur en C++ 11

Une petite explication de la logique dans le code.

struct ResourceManager{ 
    std::mutex mux; 
    std::unique_lock lock{mux}; 
    std::condition_variable bell; 

    void addResource(/*some Resource*/){ 
    lock.lock(); 
    //add resource 
    lock.unlock(); 
    bell.notify_one(); //notifies waiting consumer threads to consume 
    } 

    T getResource(){ 
    while(true){ 
     lock.lock(); 
     if(/*resource is available*/){ 
     //remove resource from the object 
     lock.unlock(); 
     return resource; 
     }else{ 
     //new unique lock mutex object wmux creation 
     lock.unlock(); //problem line 
     bell.wait(wmux); //waits until addResource rings the bell 
     continue; 
     } 
    } 
    } 

}; 

Supposons que le scénario suivant:
-Deux fils, T1, T2, appeler addResource, getResource presque simultanément. -T2 verrouille le mutex et découvre qu'il n'y a plus de ressources disponibles,
donc il doit bloquer jusqu'à ce qu'une nouvelle ressource soit disponible.
Donc, il déverrouille le mutex et met en place la cloche d'attente.

-T1 exécute une correspondance plus rapide. Lorsque le mutex est déverrouillé, il ajoute immédiatement la ressource, et avant que T2 ne mette en place la cloche d'attente,
T1 a déjà sonné la cloche, qui n'indique personne.

-T2 attend indéfiniment que la cloche sonne, mais aucune autre ressource n'est ajoutée.

Je fais l'hypothèse qu'un fil bloquant un mutex, peut être le seul pour le déverrouiller. Donc, si j'essayais d'appeler bell.wait avant de déverrouiller le mutex, le mutex ne pourrait jamais être déverrouillé.

Je ne souhaite pas utiliser de solution d'attente ou de vérification multiple si possible.
Alors, comment pourrais-je résoudre ce problème en C++ 11?

+0

Peut-être pas lié à votre problème, mais s'il vous plaît utiliser des gardes de verrouillage comme 'std :: unique_lock' pour le verrouillage/déverrouillage des mutex. –

+0

lock est un unique_lock –

+0

Qu'est-ce que 'wmux'? Vous devriez verrouiller 'lock', et effectuer' bell.wait (lock) ', pour éviter une condition de concurrence. – erenon

Répondre

1
lock.unlock(); //problem line 
    bell.wait(wmux); //waits until addResource rings the bell 

Oui, c'est la ligne de problème, en effet.

Pour utiliser correctement une variable de condition comme prévu, vous ne pas déverrouiller un mutex avant wait() sur sa variable ing associée à un problème. wait() sur une variable de condition le déverrouille atomiquement pendant la durée de l'attente, et réapprovisionne le mutex une fois que le thread a été notify() -ed. Les commandes déverrouiller et attendre et réveiller après notification et verrouillage sont des opérations atomiques.

Tous les notify() doivent être émis lorsque le mutex est verrouillé. Tous les wait() sont également effectués pendant que le mutex est complètement verrouillé. Étant donné que notify(), comme je l'ai mentionné, est atomique, toutes les opérations liées au mutex sont atomiques et entièrement séquencées, y compris la gestion des ressources protégées par le mutex, et la notification des threads via des variables de condition, également protégées par un mutex. .

Il existe des modèles de conception qui peuvent être implémentés pour notifier des variables de condition sans utiliser de protection mutex. Mais ils sont beaucoup plus difficiles à implémenter correctement et atteignent toujours une sémantique sûre pour les threads. Avoir toutes les opérations de variable de condition également protégées par le mutex, en plus de tout ce que le mutex protège, est beaucoup plus simple à implémenter.

+0

Merci d'avoir répondu. Donc, si j'ai bien compris, dans addResource, bell.notify_one() devrait être appelé avant que le mutex ne soit déverrouillé. De plus, dans getResource, je devrais supprimer lock.unlock(), et utiliser bell.wait (lock) au lieu de bell.wait (wmux); Ai-je raison? –

+0

Cela semble correct; Bien sûr, cela dépend des détails de l'implémentation, car vous avez seulement résumé votre code dans votre question. Note [l'exigence de wait()] (http://en.cppreference.com/w/cpp/thread/condition_variable/wait): "lock - un objet de type std :: unique_lock , qui doit être *** verrouillé par le filetage actuel *** ". –

1

std::condition_variable::wait doit être passé à std::unique_lock verrouillé sur votre mutex. wait déverrouillera le mutex dans le cadre de son fonctionnement, et le verrouillera de nouveau avant son retour. La manière normale d'utiliser les verrous de sécurité comme std::lock_guard et std::unique_lock est de les construire localement et de laisser leur constructeur verrouiller votre mutex et leur destructeur le déverrouiller.

En outre, vous pouvez éviter la boucle externe while dans votre code d'origine en fournissant un prédicat à std::condition_variable::wait.

struct ResourceManager { 
    std::mutex mux; 
    std::condition_variable bell; 

    void addResource(T resource) 
    { 
    std::lock_guard<std::mutex> lock{mux}; 
    // Add the resource 
    bell.notify_one(); 
    } 

    T getResource() 
    { 
    std::unique_lock<std::mutex> lock{mux}; 
    bell.wait(lock, [this](){ return resourceIsAvailable(); }); 
    return // the ressource 
    } 
}; 
+0

Merci pour votre temps, votre réponse décrit bien ce qu'une autre personne a déjà répondu ici avant. Dommage qu'il ait supprimé son post: S –