2010-07-19 4 views
4

Je cherche donc à utiliser une simple file d'attente producteur/consommateur en C++. Je vais finir par utiliser boost pour le threading mais cet exemple n'utilise que pthreads. Je finirai par utiliser une approche beaucoup plus orientée objet, mais je pense que cela obscurcirait les détails qui m'intéressent en ce moment.Quels sont les problèmes avec cette implémentation producteur/consommateur?

Quoi qu'il en soit les problèmes particuliers que je suis inquiet au sujet sont

  1. Depuis ce code utilise push_back et pop_front de std :: deque - qu'il fait probablement l'allocation et désallocation des données sous-jacentes dans différents threads - je crois c'est mauvais (comportement indéfini) - quel est le moyen le plus simple d'éviter cela?
  2. Rien n'est marqué comme volatile. Mais les bits importants sont protégés par mutex. Ai-je besoin de marquer quoi que ce soit comme volatile et si oui quoi? - Je ne pense pas que je le fasse car je crois que le mutex contient des barrières de mémoire appropriées etc., mais je ne suis pas sûr.

Y at-il d'autres problèmes flagrants?

Quoi qu'il en soit Heres le code:

#include <pthread.h> 
#include <deque> 
#include <iostream> 

struct Data 
{ 
    std::deque<int> * q; 
    pthread_mutex_t * mutex; 
}; 

void* producer(void* arg) 
{ 
    std::deque<int> &q = *(static_cast<Data*>(arg)->q); 
    pthread_mutex_t * m = (static_cast<Data*>(arg)->mutex); 

    for(unsigned int i=0; i<100; ++i) 
    { 
    pthread_mutex_lock(m); 
    q.push_back(i); 
    std::cout<<"Producing "<<i<<std::endl; 
    pthread_mutex_unlock(m); 
    } 
    return NULL; 
} 

void* consumer(void * arg) 
{ 
    std::deque<int> &q = *(static_cast<Data*>(arg)->q); 
    pthread_mutex_t * m = (static_cast<Data*>(arg)->mutex); 

    for(unsigned int i=0; i<100; ++i) 
    { 
    pthread_mutex_lock(m); 
    int v = q.front(); 
    q.pop_front(); 
    std::cout<<"Consuming "<<v<<std::endl; 
    pthread_mutex_unlock(m); 
    } 
    return NULL; 
} 

int main() 
{ 
    Data d; 

    std::deque<int> q; 
    d.q = &q; 

    pthread_mutex_t mutex; 
    pthread_mutex_init(&mutex, NULL); 
    d.mutex = & mutex; 

    pthread_t producer_thread; 
    pthread_t consumer_thread; 

    pthread_create(&producer_thread, NULL, producer, &d); 
    pthread_create(&consumer_thread, NULL, consumer, &d); 

    pthread_join(producer_thread, NULL); 
    pthread_join(consumer_thread, NULL); 
} 

EDIT:

J'ai fini par jeter cette mise en œuvre, j'utilise maintenant une version modifiée du code de here par Anthony Williams. Ma version modifiée peut être trouvée here Cette version modifiée utilise une approche basée sur une variable de condition plus sensible.

+2

Le problème principal est que vous nous demandez d'évaluer une solution où vous allez déchirer et jeter le thread sous-jacent, puis aussi réécrire le tout à partir de zéro d'une manière «beaucoup plus OO». Je pense que cela est connu comme une évaluation prématurée :-) – paxdiablo

+0

@paxdiablo: Ses questions spécifiques reposent sur leurs propres mérites. Mais +1 pour la terminologie humoristique ... –

+0

@paxdiablo J'essaie juste d'éviter les réponses inutiles de la forme "use objects" ou "use boost". Je suis bien conscient que je peux rencontrer d'autres problèmes lorsque je migre mon code - mais les réponses que j'obtiens ici resteront pertinentes dans le code modifié. –

Répondre

1
  1. Il est parfaitement valable pour allouer de la mémoire dans un fil et d'une connexion dans une autre si les deux fils sont dans le même processus. L'utilisation d'un mutex pour protéger l'accès à la mémoire devrait fournir la configuration d'accès à la mémoire correcte.

EDIT: La seule autre chose à penser est la nature du producteur et du consommateur. Votre exemple synthétisé manque certaines des subtilités impliquées dans une implémentation réelle.Par exemple, comment synchroniserez-vous le producteur avec le consommateur s'il ne fonctionne pas exactement au même rythme? Vous pourriez envisager d'utiliser quelque chose comme un tuyau ou une file d'attente de système d'exploitation au lieu d'une deque afin que le consommateur puisse bloquer à la lecture s'il n'y a pas de données prêtes à traiter.

+1

Je pense que l'édition tombe probablement dans la zone d'un point clé. L'exemple de code présenté fonctionnera probablement/peut-être/peut-être, mais il n'y a vraiment aucune garantie qu'il y aura quoi que ce soit dans la file d'attente quand le thread consommateur commence. Si cela arrive, 'queue.front()' et queue.pop_front() 'auront le mauvais type de comportement indéfini. – msandiford

+0

@sprong: Vous avez absolument raison. Savoir quand s'arrêter et quand continuer à lire est un problème critique dans cette conception .. et comme vous le remarquez actuellement très cassé ... J'ai juste eu de la chance dans les temps que je l'ai couru qu'il ne s'est pas effondré. Pensez que je pourrais devoir fermer ceci et créer un cas plus détaillé, –

+0

@Michael: Plusieurs points supplémentaires. Vous devez déverrouiller dès que l'opération de file d'attente est terminée. Disons que le travail à faire est symbolisé par l'instruction 'cout <<', et qu'il faut du temps pour l'exécuter. Si la file d'attente est verrouillée pendant toute la durée, le producteur ne pourra plus accéder à la file d'attente tant que le consommateur n'aura pas terminé un élément antérieur (c'est-à-dire qu'il ne pourra pas effectuer un travail utile simultanément). 2. Besoin d'un mécanisme pour arrêter le consommateur, c'est-à-dire qu'il n'y a plus d'articles après celui-ci. – rwong

2

Depuis ce code est d'utiliser push_back et pop_front de std :: deque - il est probablement faire l'allocation et désallocation des données sous-jacentes dans différents threads - Je crois que cela est mauvais (comportement non défini) - Quelle est la meilleure façon d'éviter ce?

Tant qu'un seul thread peut modifier le conteneur à la fois, c'est correct.

Rien n'est marqué comme volatile. Mais les bits importants sont protégés par mutex. Ai-je besoin de marquer quoi que ce soit comme volatile et si oui quoi? - Je ne pense pas que je le fasse car je crois que le mutex contient des barrières de mémoire appropriées etc., mais je ne suis pas sûr.

Tant que vous contrôlez correctement l'accès au conteneur en utilisant un mutex, il n'a pas besoin d'être volatile (cela dépend de votre bibliothèque de threads, mais ce ne serait pas une très bonne mutex si elle n » ai pas t fournir une barrière de mémoire correcte).

Questions connexes