Programación concurrente (II)

 Artículos de la serie:


Paralelización de algoritmos


Basándonos en la explicación proporcionada en el primer post de esta serie, es claro que la función std::async nos permitirá paralelizar de forma directa algoritmos tales como for_each, accumulate o quick_sort.

Un aspecto fundamental a tener en cuenta a la hora de paralelizar algoritmos es el de evitar la incidencia de data races, es decir, el acceso simultáneo a la misma localización en memoria por parte de diferentes hilos, siendo al menos uno de dichos accesos de escritura. No es éste un problema con el que debamos lidiar en este post, por lo que pospondremos su análisis por el momento (véase el artículo 5 de esta serie para más detalles).

Programación concurrente (I)

 Artículos de la serie:


Última actualización de la serie: 14 de agosto de 2021

Introducción básica: clase std::thread y función std::async


El estándar ISO C++ 2011 introdujo la clase std::thread [1] en la biblioteca <thread> con el fin de permitir la creación de nuevos hilos de ejecución, de forma que los distintos hilos de un proceso puedan llevarse a efecto de forma concurrente. En particular, dicha clase proporciona un constructor variádico de signatura

   template<typename Func, typename ...Args>    explicit thread(Func&& f, Args&&... args);

El abecé de la plantilla vector<>

Última actualización del post: Octubre de 2020

Introducción


Como sabemos, la estructura estándar std::vector<> se considera el contenedor dinámico por defecto en el lenguaje C++. Ésta representa un array alojado en el free store, cuyo tamaño puede aumentar o disminuir a conveniencia durante el tiempo de ejecución de un programa. El hecho fundamental de que todos los elementos almacenados en el vector ocupen entradas contiguas en memoria virtual convierte a esta estructura en especialmente eficiente dado el modo de funcionamiento de los distintos niveles de memoria caché de nuestra computadora (véase este post anterior en relación a la localidad espacial y temporal de un programa), en contraste con el comportamiento típico de, por ejemplo, una lista (ver la figura inferior).


En este artículo discutiremos brevemente el funcionamiento del método principal de esta estructura, de signatura void vector<T>::push_back(T const& val), el cual introduce una copia de su argumento val al fondo del vector. Simplificaremos enormemente nuestra presentación omitiendo detalles finos de la implementación del contenedor, que incluirían el uso de técnicas avanzadas de alojamiento en memoria o la posibilidad de utilizar la semántica de movimiento como método para evitar copias innecesarias de objetos. Nuestra implementación resultará, por tanto, poco eficiente y de utilidad ciertamente limitada, pero de claro valor pedagógico.