Compartir a través de


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.

at

end

operator[]

begin

front

push_back

back

grow_by

rbegin

capacity

grow_to_at_least

rend

empty

max_size

size

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.

assign

reserve

clear

resize

operator=

shrink_to_fit

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.

empty

push

get_allocator

try_pop

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.

clear

unsafe_end

unsafe_begin

unsafe_size

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

operator++

Avanza hasta el siguiente elemento de la cola. Este operador se sobrecarga para proporcionar semántica previa y posterior al incremento.

operator*

Recupera una referencia al elemento actual.

operator->

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

local

Recupera una referencia a la variable local asociada al contexto del subproceso actual.

clear

Quita todas las variables locales de subproceso del objeto combinable.

combine

combine_each

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

Referencia

Clase concurrent_vector

Clase concurrent_queue

Clase combinable