2012-01-14 4 views
1

Comme std::vector n'est pas thread-safe, j'essayais de construire un très simple encapsulation autour de lui qui le rend thread-safe.Release boost :: mutex de destructor

Cela fonctionne plutôt bien, mais il y a un petit problème. Lorsque l'instance de la classe est en train d'être détruite et qu'un autre thread essaie toujours d'en lire des données, le thread reste suspendu indéfiniment dans le boost::mutex::scoped_lock lock(m_mutex);

Comment résoudre ce problème? Le mieux est de simplement déverrouiller le mutex afin que le thread qui s'y trouve puisse continuer à s'exécuter. Je n'ai pas défini de destructeur car jusqu'à présent, ce n'était pas nécessaire.

Voici mon code. Notez qu'il y a plus de méthodes que celles montrées ici, cela a été simplifié.

template<class T> 
class SafeVector 
{ 
    public: 
    SafeVector(); 
    SafeVector(const SafeVector<T>& other); 

    unsigned int size() const; 
    bool empty() const; 

    void clear(); 
    T& operator[] (const unsigned int& n); 

    T& front(); 
    T& back(); 

    void push_back(const T& val); 
    T pop_back(); 

    void erase(int i); 

    typename std::vector<T>::const_iterator begin() const; 
    typename std::vector<T>::const_iterator end() const; 

    const SafeVector<T>& operator= (const SafeVector<T>& other); 

    protected: 
    mutable boost::mutex m_mutex; 
    std::vector<T> m_vector; 

}; 

template<class T> 
SafeVector<T>::SafeVector() 
{ 

} 

template<class T> 
SafeVector<T>::SafeVector(const SafeVector<T>& other) 
{ 
    this->m_vector = other.m_vector; 
} 

template<class T> 
unsigned int SafeVector<T>::size() const 
{ 
    boost::mutex::scoped_lock lock(m_mutex); 
    return this->m_vector.size(); 
} 

template<class T> 
bool SafeVector<T>::empty() const 
{ 
    boost::mutex::scoped_lock lock(m_mutex); 
    return this->m_vector.empty(); 
} 

template<class T> 
void SafeVector<T>::clear() 
{ 
    boost::mutex::scoped_lock lock(m_mutex); 
    return this->m_vector.clear(); 
} 

template<class T> 
T& SafeVector<T>::operator[] (const unsigned int& n) 
{ 
    boost::mutex::scoped_lock lock(m_mutex); 
    return (this->m_vector)[n]; 
} 

template<class T> 
T& SafeVector<T>::front() 
{ 
    boost::mutex::scoped_lock lock(m_mutex); 
    return this->m_vector.front(); 
} 

template<class T> 
T& SafeVector<T>::back() 
{ 
    boost::mutex::scoped_lock lock(m_mutex); 
    return this->m_vector.back(); 
} 

template<class T> 
void SafeVector<T>::push_back(const T& val) 
{ 
    boost::mutex::scoped_lock lock(m_mutex); 
    return this->m_vector.push_back(val); 
} 

template<class T> 
T SafeVector<T>::pop_back() 
{ 
    boost::mutex::scoped_lock lock(m_mutex); 
    T back = m_vector.back(); 
    m_vector.pop_back(); 
    return back; 
} 

template<class T> 
void SafeVector<T>::erase(int i) 
{ 
    boost::mutex::scoped_lock lock(m_mutex); 
    this->m_vector.erase(m_vector.begin() + i); 
} 

template<class T> 
typename std::vector<T>::const_iterator SafeVector<T>::begin() const 
{ 
    return m_vector.begin(); 
} 

template<class T> 
typename std::vector<T>::const_iterator SafeVector<T>::end() const 
{ 
    return m_vector.end(); 
} 

Modifier je dois changer ma définition. Le conteneur n'est clairement pas sûr pour les threads comme indiqué précédemment. Ce n'est pas censé le faire - même si la nomenclature est trompeuse. Bien sûr, vous pouvez faire des choses qui ne sont pas du tout thread-safe! Mais un seul fil écrit dans le conteneur, 2 ou 3 sont lus. Cela fonctionne bien jusqu'à ce que j'essaie d'arrêter le processus. Je dois dire qu'un moniteur aurait été mieux. Mais le temps s'épuise et je ne peux pas changer cela jusque-là.

Toute idée est appréciée! Merci et salutations.

+3

Je pense que vous avez des problèmes de filetage à un niveau supérieur. Cela semble être une erreur pour le thread 1 de penser qu'il est légitime de détruire un objet auquel le thread 2 a une référence et croit qu'il peut en lire. Et si le thread 1 réussissait réellement à détruire complètement l'objet et que _then_ thread 2 essayait de le lire? –

+1

Le constructeur de copie ne devrait-il pas également verrouiller le vecteur * other *? –

+0

Je serais intéressé de voir la mise en œuvre de l'opérateur d'affectation de copie, aussi. Il serait facile de l'obtenir de façon spectaculaire. –

Répondre

1

EDIT: Mis à jour pour être plus complet.

D'autres ont souligné les défauts de votre «sécurité de fil»; Je vais essayer de répondre à votre question. La seule bonne façon de faire ce que vous avez décidé de faire est de vous assurer que tous vos threads ont été arrêtés avant d'essayer de détruire le vecteur lui-même.

Une méthode courante que j'ai utilisée est d'utiliser simplement RAII pour définir l'ordre de construction et de destruction.

void doSomethingWithVector(SafeVector &t_vec) 
{ 
    while (!boost::this_thread::interruption_requested()) 
    { 
    //operate on t_vec 
    } 
} 

class MyClassThatUsesThreadsAndStuff 
{ 
    public: 
    MyClassThatUsesThreadsAndStuff() 
     : m_thread1(&doSomethingWithVector, boost::ref(m_vector)), 
     m_thread2(&doSomethingWithVector, boost::ref(m_vector)) 
    { 
     // RAII guarantees that the vector is created before the threads 
    } 

    ~MyClassThatUsesThreadsAndStuff() 
    { 
     m_thread1.interrupt(); 
     m_thread2.interrupt(); 
     m_thread1.join(); 
     m_thread2.join(); 
     // RAII guarantees that vector is freed after the threads are freed 
    } 

    private: 
    SafeVector m_vector; 
    boost::thread m_thread1; 
    boost::thread m_thread2; 
}; 

Si vous êtes à la recherche d'une structure de données de fil plus complète de sécurité qui permet de multiples lecteurs et écrivains, ne hésitez pas à consulter une file d'attente je l'ai écrit en utilisant boost Threads un certain temps.

http://code.google.com/p/crategameengine/source/browse/trunk/include/mvc/queue.hpp

+0

merci pour votre réponse. Je sais à propos de RAII. le "problème" ici est que les jointures ne reviennent pas toujours ... si j'ignore l'implémentation de votre file d'attente, le principe me semble identique: mettre un scoped_lock partout où vous effectuez des opérations sur le conteneur - sauf que ma mise en oeuvre est manque de la partie annulation. – Atmocreations

+0

J'ai des lectures bloquantes dans ma classe de file d'attente, ce qui explique pourquoi l'annulation est nécessaire. Vous n'avez aucune fonctionnalité "Bloquer et attendre les données" de votre classe. Cela m'amène à supposer que l'erreur réelle est que le Mutex auquel vous essayiez d'accéder a été détruit avant/pendant un verrou. La seule façon dont cela pourrait arriver est que les vecteurs soient détruits avant le fil. Avec mon exemple ci-dessus, vous avez maintenant un exemple d'annulation ainsi que des garanties que le mutex ne peut pas être détruit avant que sa vie utile soit terminée. – lefticus

Questions connexes