c++ visual-studio-2010 concurrency c++11 blockingqueue

¿Hay contenedores concurrentes en C++ 11?



visual-studio-2010 concurrency (5)

En particular, estoy buscando una cola de bloqueo. ¿Hay algo así en C ++ 11? Si no, ¿cuáles son mis otras opciones? Realmente ya no quiero bajar al nivel del hilo. Demasiado propenso a errores.


C ++ 11 no proporciona contenedores concurrentes por sí mismo. Sin embargo, hay opciones de biblioteca. Además de la PPL ya mencionada, no olvide la biblioteca Intel TBB.

Tiene una queue concurrente, hash_map , set y vector implementation. Pero no solo es una biblioteca de contenedores segura para subprocesos, sino que también viene con una versión paralela de algoritmos estándar (for-loop, reduce, sort, ...).

Sitio web de Intel TBB


Las interfaces de los contenedores simplemente no han sido diseñadas con este objetivo. Para las interfaces que usan, un bloqueo visible para el cliente es realmente la única forma en que puede lograr esto mientras garantiza la corrección y el comportamiento predecible. También sería terriblemente ineficiente porque la cantidad de adquisiciones sería muy alta (en relación con una buena implementación).

Solución 1

Pase por valor (cuando corresponda).

Solución 2

Cree una colección de implementaciones atornillables simples que puede usar para pasar contenedores mientras mantiene un bloqueo de alcance (considérelo pseudoc ++):

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; };

luego, la persona que llama empareja el bloqueo con la colección y luego actualiza las interfaces para usar (pasar) el tipo de contenedor donde corresponda. Es solo la extensión de clase de un hombre pobre.

Este contenedor cerrado es un ejemplo simple, y hay algunas otras variantes. Esta es la ruta que elegí porque realmente te permite usar el nivel de granularidad que es ideal para tu programa, aunque no es tan transparente (sintácticamente) como los métodos bloqueados. También es relativamente fácil adaptar los programas existentes. Al menos se comporta de manera predecible, a diferencia de las colecciones con bloqueos internos.

Otra variante sería:

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;

... donde podría usarse un tipo similar a t_locked_collection para exponer la colección subyacente. No implica que ese enfoque sea infalible, solo tonto.


Me sorprende que nadie mencionó moodycamel::ConcurrentQueue . Lo hemos estado utilizando durante bastante tiempo y funciona muy bien. Es específico que su implementación está libre de bloqueos, lo que de inmediato brinda una velocidad enorme. Otras razones para usarlo (citando desde el sitio oficial):

No hay muchas colas plenas sin bloqueo para C ++. Boost tiene uno, pero está limitado a objetos con operadores de asignación triviales y destructores triviales, por ejemplo. La cola TBB de Intel no está bloqueada, y también requiere constructores triviales. Hay muchos documentos académicos que implementan colas sin bloqueos en C ++, pero el código fuente utilizable es difícil de encontrar, y lo prueba aún más.

Algunos puntos de referencia y comparaciones están disponibles here , here y here .


Mi versión de una concurrencia concurrente del espacio de nombres del mapa sin orden {

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); } }; }


Según Diego Dagum del Equipo de Visual C ++ de Microsoft :

Una pregunta recurrente (bueno, una de las tantas) es acerca de los contenedores STL y si son seguros para subprocesos.

Tomando las palabras de Stephan aquí, la realidad es que no lo son, no como un error sino como una característica: tener cada función miembro de cada contenedor STL adquiriendo un bloqueo interno anularía el rendimiento. Como biblioteca de uso general, altamente reutilizable, en realidad tampoco proporcionaría corrección: el nivel correcto para colocar bloqueos está determinado por lo que el programa está haciendo. En ese sentido, las funciones de los miembros individuales no tienden a ser de ese nivel correcto.

La Parallel Patterns Library (PPL) incluye varios contenedores que proporcionan acceso seguro a sus elementos:

  • La clase concurrent_vector es una clase de contenedor de secuencia que permite el acceso aleatorio a cualquier elemento. Permite anexos seguros de concurrencia, acceso a elementos, acceso a iteradores y operaciones de cruce de iteradores.
  • La clase concurrent_queue es una clase de contenedor de secuencia que permite el acceso first-in-first-out a sus elementos. Permite un conjunto limitado de operaciones seguras a la concurrencia, como push y try_pop, por nombrar algunas.

Algunas muestras here .

También es interesante: http://www.justsoftwaresolutions.co.uk/threading/implementing-a-thread-safe-queue-using-condition-variables.html .