vectores que para mas imagenes illustrator gratis freepik diseƱo descargar animales c++ multithreading c++11 vector

c++ - que - Vector seguro de subprocesos



vectores imagenes (3)

Permítanme comenzar diciendo que he leído la mayoría de SO y otros temas sobre el tema.

Por la forma en que entiendo las cosas, std :: vector reasignará la memoria al rechazar elementos nuevos, que es mi caso, a menos que haya reservado suficiente espacio (que no es mi caso).

Lo que tengo es un vector de std :: shared_ptr, y ese vector contiene objetos únicos (o más correctamente, punteros a objetos únicos en el vector).

El manejo de esos objetos a través de punteros se ajusta a una clase Factory & Handler, pero se puede acceder a los punteros desde fuera de la clase contenedora y se pueden modificar los valores de los miembros. No hay eliminación en ningún momento.

Si estoy entendiendo correctamente los problemas planteados en preguntas anteriores sobre seguridad estándar y de subprocesos, agregar (push_back) nuevos objetos puede invalidar punteros anteriores, ya que el vector internamente puede reasignar memoria y copiar todo, lo que sería un desastre. para mi.

Mis intenciones son leer de ese vector, a menudo modificando objetos a través de los punteros, y agregar nuevos elementos al vector, desde subprocesos que se ejecutan de forma asincrónica.

Asi que,

  1. Usar atomic o mutexes no es suficiente? Si retrocedo desde un hilo, otro hilo que maneje un objeto a través de un puntero puede terminar teniendo un objeto no válido?
  2. ¿Hay una biblioteca que pueda manejar esta forma de problemas de MT? El que sigo leyendo es TBB de Intel, pero como ya estoy usando C ++ 11, me encantaría mantener los cambios al mínimo, incluso si eso significa más trabajo de mi parte, quiero aprender en el proceso. , no solo copiar y pegar
  3. Además de bloquear el acceso mientras se modifican los objetos, me gustaría el acceso asíncrono de lectura paralela al vector que no será invalidado por push_backs. ¿Cómo puedo lograr eso?

Si tiene alguna importancia, todo lo anterior está en linux (debian jessie) usando gcc-4.8 con c ++ 11 habilitado.

Estoy abierto a usar bibliotecas mínimamente invasivas.

Muchas gracias de antemano :-)


agregar (push_back) nuevos objetos puede invalidar punteros anteriores ...

No, esta operación no invalida ningún puntero anterior, a menos que esté refiriéndose a direcciones dentro de la administración de datos internos de los vectores (que claramente no es su escenario).
Si almacena punteros crudos, o std::shared_ptr allí, los simplemente se copiarán y no se invalidarán.

Como se menciona en los comentarios, un std::vector no es muy adecuado para garantizar la seguridad de los hilos para patrones de productores / consumidores por una serie de razones. ¡Ni almacenar punteros crudos para hacer referencia a las instancias activas es!

Una cola será mucho mejor para apoyar esto. En cuanto a las normas, puede usar std::deque para tener puntos de acceso ceratain ( front() , back() ) para el productor / consumidor.

Para que la hebra de estos puntos de acceso sea segura (para valores de empuje / reventado) puede envolverlos fácilmente con su propia clase y usar un mutex a lo largo, para asegurar las operaciones de inserción / eliminación en la referencia de la cola compartida.
El otro punto (y principal, a partir de su pregunta) es: administrar la propiedad y la duración de las instancias contenidas / referenciadas . También puede transferir la propiedad al consumidor, si es adecuado para su caso de uso (por lo tanto, desconexión de la sobrecarga con, por ejemplo, std::unique_ptr ), consulte a continuación ...

Además, puede tener un semáforo (variable de condición) para notificar al hilo del consumidor que hay nuevos datos disponibles.

''1. Usar atomic o mutexes no es suficiente? Si retrocedo desde un hilo, ¿otro hilo que maneje un objeto mediante un puntero puede terminar teniendo un objeto inválido?

La duración (y, por lo tanto, el uso seguro de subprocesos) de las instancias almacenadas en la cola (contenedor compartido) debe gestionarse por separado (por ejemplo, utilizando punteros inteligentes como std::shared_ptr o std::unique_ptr almacenados allí).

''2. Hay una biblioteca ...''

Se puede lograr todo muy bien con los mecanismos de biblioteca estándar existentes en mi humilde opinión.

En cuanto al punto 3. mira lo que está escrito arriba. Como lo que puedo decir más sobre esto, parece que estás pidiendo algo así como un mutex rw_lock . Puede proporcionar un sustituto para esto con una variable de condición adecuada.

Siéntase libre de pedir más aclaraciones ...


Si siempre agregará nuevos elementos al contenedor y luego accederá a ellos, lo que podría serle útil es un vector con otro direccionamiento indirecto, de modo que en lugar de cambiar el buffer interno por uno más grande, el espacio asignado una vez nunca se libera y un nuevo espacio se agrega de alguna manera de manera segura.

Por ejemplo, puede verse así:

concurrent_vector<Object*>: size_t m_baseSize = 1000 size_t m_size = 3500 part* m_parts[6] = { part* part1, ----> Object*[1000] part* part2, ----> Object*[2000] part* part3, ----> Object*[4000] NULL, ... }

La clase contiene una matriz fija de punteros a trozos individuales de memoria con los elementos, con un tamaño exponencialmente creciente. Aquí el límite es de 6 partes, entonces 63000 elementos, pero esto se puede cambiar fácilmente.

El contenedor comienza con todos los punteros de partes establecidos en NULL. Si se agrega un elemento, se crea el primer trozo, con tamaño m_baseSize , aquí 1000 y se guarda en m_parts[0] . Los artículos subsecuentes están escritos allí.

Cuando el trozo está lleno, se asigna otro tampón, con el doble de tamaño que el anterior (2000), y se almacena en m_parts[1] . Esto se repite según sea necesario.

Todo esto se puede hacer usando operaciones atómicas, pero eso es, por supuesto, complicado. Puede ser más sencillo si todos los escritores pueden estar protegidos por un mutex y solo los lectores son totalmente concurrentes (por ejemplo, si la escritura es una operación mucho más rara). Todos los subprocesos de lector siempre ven NULL en m_parts[i] o NULL en uno de los búferes, o un puntero válido. Los elementos existentes nunca se mueven en la memoria, se invalidan ni nada.

En lo que respecta a las bibliotecas existentes, es posible que desee ver bloques de creación de subprocesos de Intel, en particular su clase de vector concurrente . Según se informa, tiene estas características:

  • Acceso aleatorio por índice. El índice del primer elemento es cero.
  • Múltiples hilos pueden hacer crecer el contenedor y anexar nuevos elementos al mismo tiempo.
  • Hacer crecer el contenedor no invalida los iteradores o índices existentes.

Al volver a leer la pregunta, la situación parece un poco diferente.

std::vector es adecuado para almacenar objetos a los que tendrías que mantener referencias, ya que un push_back puede invalidar todas las referencias a los objetos almacenados. Pero, está almacenando un montón de std::shared_ptr .

El std::shared_ptr almacenado en el interior debe manejar el cambio de tamaño correctamente (se están moviendo, pero no los objetos a los que apuntan) siempre y cuando, en los hilos, no mantenga referencias a std::shared_ptr s almacenadas dentro del vector, pero guardas copias de ellos.

Tanto usando std::vector como std::deque tiene que sincronizar el acceso a la estructura de datos, ya que un push_back , aunque no invalida la referencia, altera las estructuras internas del deque , y por lo tanto no puede ejecutarse simultáneamente con un acceso deque

OTOH, std::deque es probablemente mejor adaptado de todos modos por razones de rendimiento; en cada cambio de tamaño, se está moviendo mucho std::shared_ptr , que puede tener que hacer un incremento / decremento bloqueado al recuento en caso de copiar / eliminar (si se mueven, como deberían, esto debería ser eliminado), pero YMMV ).

Pero lo más importante, si su uso de std::shared_ptr es solo para evitar los posibles movimientos en el vector, puede descartarlo por completo al usar un deque , ya que las referencias no están invalidadas, por lo que puede almacenar sus objetos directamente en el deque en lugar de usar la asignación de montón y la indirección / sobrecarga de std::shared_ptr .