En particulier, je recherche une file d'attente bloquante. Existe-t-il une telle chose en C++ 11? Si non, quelles sont mes autres options? Je ne veux vraiment plus descendre au niveau du thread moi-même. Bien trop sujet aux erreurs.
Selon Diego Dagum de l'équipe Visual C++ de Microsoft :
Une question récurrente (l'une des nombreuses) concerne les conteneurs STL et si elles sont thread-safe.
Prenant les mots de Stephan ici, la réalité est qu’ils ne sont pas, pas comme un bug mais comme une caractéristique: avoir chaque fonction membre de chaque STL L’acquisition d’un conteneur par un conteneur annihilerait les performances. Comme une bibliothèque à usage général, hautement réutilisable, elle n’aurait en fait pas fournir l'exactitude soit: le niveau correct pour placer les verrous est déterminé par ce que le programme fait. En ce sens, individu les fonctions des membres n’ont pas tendance à être aussi correctes.
La bibliothèque de modèles parallèles (PPL) inclut plusieurs conteneurs qui offrent un accès sans risque de thread à leurs éléments:
Quelques échantillons ici .
Également intéressant: http://www.justsoftwaresolutions.co.uk/threading/implementing-a-thread-safe-queue-using-condition-variables.html .
C++ 11 ne fournit pas de conteneurs simultanés. Cependant, il existe des options de bibliothèque… .. Outre la PPL déjà mentionnée, n'oubliez pas la bibliothèque Intel TBB.
Il a une implémentation queue
, hash_map
, set
et vector
concurrente. Mais ce n’est pas seulement une bibliothèque de conteneurs sécurisée pour les threads, il est également livré avec une version parallèle des algorithmes standard (pour les boucles, réduire, trier, ...).
Je suis surpris que personne n'ait 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 verrouillage, ce qui apporte immédiatement une vitesse énorme. Autres raisons de l'utiliser (citant le site officiel):
Il n'y a pas beaucoup de files d'attente sans verrouillage à part entière pour C++. Renforcer en a un, mais il est limité aux objets avec des opérateurs d’affectation triviaux et destructeurs triviaux, par exemple. La file d'attente TBB d'Intel n'est pas sans verrouillage, et nécessite également des constructeurs triviaux. Il y a beaucoup de articles académiques qui implémentent des files d'attente sans verrouillage en C++, mais utilisables Le code source est difficile à trouver, et le teste encore plus.
Quelques repères et comparaisons sont disponibles ici , ici et ici .
Les interfaces des conteneurs n’ont tout simplement pas été conçues dans cet objectif. Pour les interfaces qu’ils utilisent, un verrou visible pour le client est vraiment le seul moyen de le faire tout en garantissant un comportement correct et prévisible. Cela serait également terriblement inefficace car le nombre d’acquisitions serait très élevé (par rapport à une bonne mise en œuvre).
Solution 1
Pass par valeur (le cas échéant).
Solution 2
Créez une collection d'implémentations simples que vous pouvez utiliser pour passer des conteneurs tout en maintenant un verrou d'étendue (considérez-le comme un 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 l'appelant associe le verrou à la collection, puis vous mettez à jour vos interfaces pour utiliser (passer) le type de conteneur, le cas échéant. C'est juste une extension de classe pour un homme pauvre.
Ce conteneur verrouillé est un exemple simple et il existe quelques autres variantes. C’est la voie que j’ai choisie car elle vous permet vraiment d’utiliser le niveau de granularité idéal pour votre programme, même s’il n’est pas aussi transparent (syntaxiquement) que les méthodes verrouillées. Il est également relativement facile d'adapter les programmes existants. Au moins, il se comporte de manière prévisible, contrairement aux collections avec des verrous 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 pas impliquer que cette approche est infaillible, juste infaillible.
Il n'y a pas de conteneurs simultanés dans C++ 11.
Mais la classe d'en-tête suivante fournit des conteneurs de file d'attente, de pile et de priorité simultanés à l'aide de std :: deque.
BlockingCollection est une classe de collection protégée des threads C++ 11 qui est modélisée d'après la classe .NET BlockingCollection.
Ma version d'une carte non ordonnée concurrente Accès simultané aux espaces de noms {
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);
}
};
}