Question

En particulier, je suis à la recherche d'une file d'attente de blocage. Y at-il une telle chose en C ++ 11? Sinon, quelles sont mes autres options? Je ne veux vraiment pas descendre au niveau de fil plus moi-même. Trop sujette à erreur.

Était-ce utile?

La solution

Selon Diego Dagum de l'équipe Visual C ++ de Microsoft :

Une question récurrente (ainsi, l'un des nombreux) est sur les conteneurs STL et si elles sont thread-safe.

Prendre les paroles de Stephan ici, la réalité est qu'ils ne sont pas, non pas comme bug, mais comme une caractéristique: avoir toutes les fonctions de membre de chaque STL l'acquisition d'un verrou récipient interne annihilerait la performance. Comme un usage général, une bibliothèque très réutilisable, il ne serait pas en fait fournir soit correct: le niveau correct de placer des verrous est déterminé par ce que le programme est en train de faire. En ce sens, individuel Les fonctions membres ne tendent pas à être tel niveau correct.

La bibliothèque parallèle modèles (PPL) comprend plusieurs conteneurs qui fournissent un accès thread-safe à leurs éléments:

  • concurrent_vector classe est une classe de conteneur de séquence qui permet un accès aléatoire à un quelconque élément. Elle permet append-concurrency en toute sécurité, l'accès de l'élément, iterator accès et iterator opérations traversal.
  • concurrent_queue classe est une classe de conteneur de séquence qui permet first-in, first-out l'accès à ses éléments. Il permet à un nombre limité d'opérations de sécurité, telles que la simultanéité poussée et try_pop, pour ne citer que quelques-uns.

Certains échantillons .

Aussi intéressant: http: // www. justsoftwaresolutions.co.uk/threading/implementing-a-thread-safe-queue-using-condition-variables.html .

Autres conseils

C ++ 11 ne fournit pas des conteneurs concurrents par lui-même. Cependant, il y a des options bibliothèque. Outre les PPL déjà mentionnées, ne pas oublier la bibliothèque Intel TBB.

Il a une queue concurrente, hash_map, set et la mise en œuvre vector. Mais il est non seulement une bibliothèque de conteneurs thread-safe, il est également livré avec la version parallèle des algorithmes standards (pour boucle, réduire, trier, ...).

site Intel TBB

Je suis surpris que personne n'a mentionné moodycamel :: ConcurrentQueue . Nous l'utilisons depuis un certain temps et il fonctionne très bien. Il est spécifique que sa mise en œuvre est sans verrou, ce qui donne immédiatement une vitesse énorme. D'autres raisons pour l'utiliser (citant le site officiel):

Il n'y a pas que beaucoup de files d'attente sans verrouillage à part entière pour C ++. Renforcer a un, mais il est limité à des objets avec des opérateurs d'affectation triviales et triviales, Destructeurs par exemple. La file d'attente d'Intel TBB est pas lock-libre, et exige des constructeurs triviales aussi. Il y a beaucoup documents académiques qui mettent en œuvre des files d'attente sans blocage en C ++, mais utilisable le code source est difficile à trouver, et les tests encore plus.

Quelques points de repère et des comparaisons sont disponibles ici et .

ont tout simplement pas les interfaces de conteneurs conçu avec cet objectif. Pour les interfaces qu'ils utilisent, un verrou visible pour le client est vraiment la seule façon que vous pouvez réaliser cela tout en garantissant l'exactitude et le comportement prévisible. Il serait également terriblement inefficace, car le nombre d'acquisitions serait très élevé (par rapport à une bonne mise en œuvre).

Solution 1

passe par la valeur (le cas échéant).

Solution 2

Créer une collection de simples boulonnés implémentations que vous pouvez utiliser pour passer des conteneurs tout en maintenant un verrou de champ (jugé pseudo c ++):

template <typename TCollection>
class t_locked_collection {
public:
    t_locked_collection(TCollection& inCollection, t_lock& lock) : collection(inCollection), d_lock(lock), d_nocopy() {
    }

    TCollection& collection;
    // your convenience stuff
private:
    t_scope_lock d_lock;
    t_nocopy d_nocopy;
};

puis les paires appelant la serrure avec la collecte, puis vous mettez à jour vos interfaces sur l'utilisation (passe par) le type de conteneur, le cas échéant. Il est juste l'extension de la classe d'un pauvre homme.

Ce conteneur verrouillé est un exemple simple, et il y a quelques autres variantes. C'est la route que j'ai choisi car il vous permet vraiment d'utiliser le niveau de granularité qui est idéal pour votre programme, même si elle pas aussi transparente (syntaxiquement) que les méthodes verrouillées. Il est relativement facile d'adapter les programmes existants. Au moins il se comporte d'une manière prévisible, à la différence des collections avec des serrures internes.

Une autre variante serait:

template <typename TCollection>
class t_lockable_collection {
public:
// ...
private:
    TCollection d_collection;
    t_mutex d_mutex;
};

// example:
typedef t_lockable_collection<std::vector<int> > t_lockable_int_vector;

... où un type similaire à t_locked_collection pourrait être utilisé pour exposer la collection sous-jacente. Ne veut pas dire que cette approche est à toute épreuve, tout fou résistant.

Ma version d'une carte undordered simultanée namespace concurrency {

template<typename T,typename T1>
class unordered_bucket: private std::unordered_map<T,T1>
{
mutable std::recursive_mutex m_mutex;

public:
T1 &operator [](T a)
{
    std::lock_guard<std::recursive_mutex> l(m_mutex);
    return std::unordered_map<T,T1>::operator [](a);
}

size_t size() const noexcept {
    std::lock_guard<std::recursive_mutex> l(m_mutex);
    return  std::unordered_map<T,T1>::size();
}

vector<pair<T,T1>> toVector() const
{
    std::lock_guard<std::recursive_mutex> l(m_mutex);

    vector<pair<T,T1>> ret;
    for(const pair<T,T1> &p:*this)
    {
        ret.push_back(p);
    }
    return ret;
}

bool find(const T &t) const
{
    std::lock_guard<std::recursive_mutex> l(m_mutex);
    if(this->std::unordered_map<T,T1>::find(t) == this->end())
        return false;  //not found
    return true;
}
void erase()
{
    std::lock_guard<std::recursive_mutex> l(m_mutex);
    this->unordered_map<T,T1>::erase(this->begin(),this->end());
}
void erase(const T &t)
{
    std::lock_guard<std::recursive_mutex> l(m_mutex);
    this->unordered_map<T,T1>::erase(t);
}
};

#define BUCKETCOUNT 10
template<typename T,typename T1>
class ConcurrentMap
{
std::vector<unordered_bucket<T,T1>> m_v;
public:
ConcurrentMap():m_v(BUCKETCOUNT){}   //using 10 buckets

T1 &operator [](T a)
{
    std::hash<T> h;
    return m_v[h(a)%BUCKETCOUNT][a];
}

size_t size() const noexcept {
    size_t cnt=0;

    for(const unordered_bucket<T,T1> &ub:m_v)
        cnt=cnt+ub.size();

    return  cnt;
}

vector<pair<T,T1>> toVector() const
{
    vector<pair<T,T1>> ret;
    for(const unordered_bucket<T,T1> &u:m_v)
    {
        const vector<pair<T,T1>> &data=u.toVector();
        ret.insert(ret.end(),data.begin(),data.end());
    }
    return ret;
}

bool find(const T &t) const
{
    for(const unordered_bucket<T,T1> &u:m_v)
        if(true == u.find(t))
            return true;
    return false;
}
void erase()
{
    for(unordered_bucket<T,T1> &u:m_v)
        u.erase();
}
void erase(const T &t)
{
    std::hash<T> h;
    unordered_bucket<T,T1> &ub = m_v[h(t)%BUCKETCOUNT];
    ub.erase(t);
}
};
}

Il n'y a pas des conteneurs simultanés en C ++ 11.

Mais la classe d'en-tête suivant fournit la file d'attente en même temps, la pile et les conteneurs prioritaires en utilisant std :: deque.

BlockingCollection est un C ++ 11 thread-safe de classe collection qui est calquée sur la BlockingCollection .NET classe.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top