Contenedores y objetos paralelos
La Biblioteca de modelos de procesamiento paralelo (PPL) incluye varios contenedores y objetos que proporcionan acceso seguro para subprocesos a sus elementos.
Un contenedor simultáneo proporciona acceso seguro para simultaneidad a las operaciones más importantes. La funcionalidad de estos contenedores se parece a la de los contenedores que proporciona la Biblioteca de plantillas estándar (STL). Por ejemplo, la clase Concurrency::concurrent_vector es similar a la clase std::vector, excepto en que la clase concurrent_vector permite anexar elementos en paralelo. Utilice contenedores simultáneos si cuenta con código paralelo que requiere tanto acceso de lectura como de escritura al mismo contenedor.
Un objeto simultáneo se comparte simultáneamente entre los componentes. Un proceso que calcula el estado de un objeto simultáneo en paralelo genera el mismo resultado que otro proceso que calcula el mismo estado en serie. La clase Concurrency::combinable es un ejemplo de un tipo de objeto simultáneo. La clase combinable permite realizar cálculos en paralelo y, a continuación, combinar estos cálculos en un resultado final. Utilice objetos simultáneos si usaría un mecanismo de sincronización, por ejemplo, una exclusión mutua, para sincronizar el acceso a una variable o un recurso compartido.
Secciones
En este tema se describen los siguientes contenedores y objetos paralelos en detalle.
Contenedores simultáneos:
Clase concurrent_vector
Clase concurrent_queue
Objetos simultáneos:
- Clase combinable
Clase concurrent_vector
La clase Concurrency::concurrent_vector es una clase de contenedor de secuencias que, al igual que la clase std::vector, permite obtener acceso a sus elementos de forma aleatoria. La clase concurrent_vector permite realizar operaciones de acceso a elementos y anexado seguras para simultaneidad. Las operaciones de anexado no invalidan los punteros ni los iteradores existentes. El acceso a los iteradores y las operaciones de cruce seguro también son seguras para simultaneidad.
Diferencias entre concurrent_vector y vector
La clase concurrent_vector es muy similar a la clase vector. La complejidad de las operaciones de anexar, obtener acceso al elementos y obtener acceso al iterador en un objeto concurrent_vector es igual que en un objeto vector. En los siguientes puntos se muestran las diferencias entre concurrent_vector y vector:
Las operaciones de anexado, acceso a elementos, acceso a iteradores y cruce seguro de iteradores de un objeto concurrent_vector son seguras para simultaneidad.
Solo puede agregar elementos al final de un objeto concurrent_vector. La clase concurrent_vector no proporciona el método insert.
Un objeto concurrent_vector no usa la semántica de transferencia de recursos cuando se le anexa.
La clase concurrent_vector no proporciona los métodos erase ni pop_back. Al igual que ocurre con vector, use el método clear para quitar todos los elementos de un objeto concurrent_vector.
La clase concurrent_vector no almacena sus elementos de forma contigua en la memoria. Por tanto, no puede usar la clase concurrent_vector de todas las maneras en que puede utilizar una matriz. Por ejemplo, en una variable con el nombre v de tipo concurrent_vector, la expresión &v[0]+2 produce un comportamiento indefinido.
La clase concurrent_vector define los métodos grow_by y grow_to_at_least. Estos métodos son similares al método resize, salvo por el hecho de que son seguros para simultaneidad.
Un objeto concurrent_vector no reubica sus elementos cuando se anexa a él o se cambia su tamaño. Esto permite que los punteros e iteradores existentes sigan siendo válidos durante las operaciones simultáneas.
El runtime no define una versión especializada de concurrent_vector para el tipo bool.
Operaciones seguras para simultaneidad
Todos los métodos que anexan a un objeto concurrent_vector o tienen acceso a él, o tienen acceso a un elemento de un objeto concurrent_vector, son seguros para simultaneidad. La excepción a esta regla es el método resize.
En la siguiente tabla se muestran los métodos y operadores comunes de concurrent_vector que son seguros para la simultaneidad.
Las operaciones que el runtime proporciona para la compatibilidad con STL, por ejemplo, reserve, no son seguras para simultaneidad. En la siguiente tabla se muestran los métodos y operadores comunes que son seguros para simultaneidad.
Las operaciones que modifican el valor de elementos existentes no son seguras para simultaneidad. Utilice un objeto de sincronización, como un objeto reader_writer_lock, para sincronizar las operaciones simultáneas de lectura y escritura al mismo elemento de datos. Para obtener más información sobre los objetos de sincronización, vea Estructuras de datos de sincronización.
Al convertir código existente que utiliza vector a código que usa concurrent_vector, las operaciones simultáneas pueden hacer que cambie el comportamiento de la aplicación. Por ejemplo, considere el siguiente programa que realiza simultáneamente dos tareas en un objeto concurrent_vector. La primera tarea anexa elementos adicionales a un objeto concurrent_vector. La segunda tarea calcula la suma de todos los elementos del mismo objeto.
// parallel-vector-sum.cpp
// compile with: /EHsc
#include <ppl.h>
#include <concurrent_vector.h>
#include <iostream>
using namespace Concurrency;
using namespace std;
int wmain()
{
// Create a concurrent_vector object that contains a few
// initial elements.
concurrent_vector<int> v;
v.push_back(2);
v.push_back(3);
v.push_back(4);
// Perform two tasks in parallel.
// The first task appends additional elements to the concurrent_vector object.
// The second task computes the sum of all elements in the same object.
parallel_invoke(
[&v] {
for(int i = 0; i < 10000; ++i)
{
v.push_back(i);
}
},
[&v] {
combinable<int> sums;
for(auto i = v.begin(); i != v.end(); ++i)
{
sums.local() += *i;
}
wcout << L"sum = " << sums.combine(plus<int>()) << endl;
}
);
}
Si bien el método end es seguro para simultaneidad, una llamada simultánea al método push_back hace que cambie el valor que end devuelve. El número de elementos que recorre el iterador no está determinado. Por tanto, este programa puede generar un resultado diferente cada vez que se ejecuta.
Seguridad de las excepciones
Si una operación de crecimiento o de asignación produce una excepción, el estado del objeto concurrent_vector deja de ser válido. El comportamiento de un objeto concurrent_vector que tiene un estado no válido no está definido, a menos que se indique lo contrario. Sin embargo, el destructor siempre libera la memoria que asigna el objeto, aunque este tenga un estado no válido.
El tipo de datos de los elementos vectoriales, _Ty, debe cumplir los siguientes requisitos. De lo contrario, el comportamiento de la clase concurrent_vector está sin definir.
No se debe iniciar el destructor.
Si se inicia el constructor predeterminado o de copia, el destructor no se debe declarar con la palabra clave virtual y debe funcionar correctamente con memoria inicializada en cero.
[Ir al principio]
Clase concurrent_queue
La clase Concurrency::concurrent_queue, al igual que la clase std::queue, permite obtener acceso a sus elementos anteriores y posteriores. La clase concurrent_queue permite poner en cola y quitar de la cola de forma segura para simultaneidad. La clase concurrent_queue también proporciona compatibilidad de iterador que no es segura para simultaneidad.
Diferencias entre concurrent_queue y queue
La clase concurrent_queue es muy similar a la clase queue. Los siguientes puntos muestran las diferencias entre concurrent_queue y queue:
Las operaciones de poner y quitar de la cola de un objeto concurrent_queue son seguras para simultaneidad.
La clase concurrent_queue proporciona compatibilidad de iterador que no es segura para simultaneidad.
La clase concurrent_queue no proporciona los métodos front ni pop. La clase concurrent_queue reemplaza estos métodos al definir el método try_pop.
La clase concurrent_queue no proporciona el método back. Por tanto, no puede referenciar al final de la cola.
La clase concurrent_queue proporciona el método unsafe_size en lugar del método size. El método unsafe_size no es seguro para simultaneidad.
Operaciones seguras para simultaneidad
Todos los métodos de un objeto concurrent_queue que ponen o quitan de la cola son seguros para simultaneidad.
En la siguiente tabla se muestran los métodos y operadores comunes de concurrent_queue que son seguros para la simultaneidad.
Si bien el método empty es seguro para simultaneidad, una operación simultánea puede hacer que la cola crezca o se reduzca antes de la devolución del método empty.
En la siguiente tabla se muestran los métodos y operadores comunes que son seguros para simultaneidad.
Compatibilidad de los iteradores
concurrent_queue proporciona iteradores que no son seguros para simultaneidad. Se recomienda usar estos iteradores únicamente para la depuración.
Un iterador concurrent_queue solo atraviesa los elementos hacia delante. En la siguiente tabla se muestran a los operadores que cada iterador admite.
Operador |
Descripción |
---|---|
Avanza hasta el siguiente elemento de la cola. Este operador se sobrecarga para proporcionar semántica previa y posterior al incremento. |
|
Recupera una referencia al elemento actual. |
|
Recupera un puntero al elemento actual. |
[Ir al principio]
Clase combinable
La clase Concurrency::combinable proporciona almacenamiento local de subprocesos reutilizable que permite realizar cálculos específicos y, a continuación, combinar estos cálculos en un resultado final. Puede considerar un objeto combinable como una variable de reducción.
La clase combinable resulta útil si se dispone de un recurso que se comparte entre varios subprocesos o tareas. La clase combinable ayuda a eliminar el estado compartido ya que proporciona acceso a recursos compartidos sin bloqueos. Por tanto, esta clase proporciona una alternativa al uso de un mecanismo de sincronización, por ejemplo una exclusión mutua, para sincronizar el acceso a datos compartidos de varios subprocesos.
Métodos y características
En la tabla siguiente se muestran algunos de los métodos más importantes de la clase combinable. Para obtener más información acerca de todos los métodos de la clase combinable, vea Clase combinable.
Método |
Descripción |
---|---|
Recupera una referencia a la variable local asociada al contexto del subproceso actual. |
|
Quita todas las variables locales de subproceso del objeto combinable. |
|
Utiliza la función combine proporcionada para generar un valor final a partir del conjunto de todos los cálculos locales de subproceso. |
La clase combinable es una clase de plantilla que se parametriza en el resultado final combinado. Si llama al constructor predeterminado, el tipo de parámetro de plantilla _Ty debe tener un constructor predeterminado y un constructor de copia. Si el tipo de parámetro de plantilla _Ty no tiene un constructor predeterminado, llame a la versión sobrecargada del constructor que toma una función de inicialización como parámetro.
Puede almacenar datos adicionales en un objeto combinable después de llamar a los métodos combine o combine_each. También puede llamar a los métodos combine y combine_each varias veces. Si no cambia ningún valor local de un objeto combinable, los métodos combine y combine_each producen el mismo resultado cada vez que se les llama.
Ejemplos
Para obtener ejemplos acerca de cómo usar la clase combinable, vea los temas siguientes:
[Ir al principio]
Temas relacionados
Cómo: Usar contenedores paralelos para aumentar la eficacia
Se muestra cómo se usan contenedores paralelos para almacenar de forma eficaz los datos y tener acceso a ellos en paralelo.Cómo: Usar la clase combinable para mejorar el rendimiento
Se muestra cómo utilizar la clase combinable para eliminar el estado compartido y, así, mejorar el rendimiento.Cómo: Usar la clase combinable para combinar conjuntos
Se muestra cómo utilizar una función combine para combinar conjuntos de datos locales de subproceso.Parallel Patterns Library (PPL)
Se describe la biblioteca PPL, que proporciona un modelo de programación imperativo que promueve la escalabilidad y facilidad de uso para desarrollar aplicaciones simultáneas.