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 de concurrency::concurrent_vector es similar a la clase de std::vector , excepto en que la clase de 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 de 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

    • Diferencias entre concurrent_vector y vector

    • Operaciones seguras para simultaneidad

    • Seguridad de las excepciones

  • Clase concurrent_queue

    • Diferencias entre concurrent_queue y queue

    • Operaciones seguras para simultaneidad

    • Compatibilidad de los iteradores

  • clase de concurrent_unordered_map

    • Concurrent_unordered_map y unordered_map de diferencias entre

    • Operaciones seguras para simultaneidad

  • clase de concurrent_unordered_multimap

  • clase de concurrent_unordered_set

  • clase de concurrent_unordered_multiset

Objetos simultáneos:

  • Clase combinable

    • Métodos y características

    • Ejemplos

Clase concurrent_vector

La clase de concurrency::concurrent_vector es una clase de contenedor de secuencias que, como la clase de std::vector , permite aleatoriamente obtener acceso a sus elementos. 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 simultaneidad- seguras. 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 = begin(v); i != end(v); ++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.

[Arriba]

Clase concurrent_queue

La clase de concurrency::concurrent_queue , como la clase de 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.

[Arriba]

clase de concurrent_unordered_map

La clase de concurrency::concurrent_unordered_map es una clase asociativa de contenedor que, como la clase de std::unordered_map , controla una secuencia de la variar- longitud de elementos de std::pair<clave const, Ty>escrito. Considere un mapa desordenado como diccionario que puede agregar un par de clave y valor a o buscar un valor por clave. Esta clase es útil cuando hay varios subprocesos o tareas que deben tener acceso simultáneamente un contenedor compartido, incrustarlo en ella, o actualizarla.

El ejemplo siguiente se muestra la estructura básica para utilizar concurrent_unordered_map. Este ejemplo inserta teclas de caracteres en el intervalo [“a”, “i”]. Dado que el orden de las operaciones es indeterminado, el valor final para cada clave también es indeterminado. Sin embargo, es seguro realizar las inserciones en paralelo.

// unordered-map-structure.cpp 
// compile with: /EHsc
#include <ppl.h>
#include <concurrent_unordered_map.h>
#include <iostream>

using namespace concurrency;
using namespace std;

int wmain() 
{
    // 
    // Insert a number of items into the map in parallel.

    concurrent_unordered_map<char, int> map; 

    parallel_for(0, 1000, [&map](int i) {
        char key = 'a' + (i%9); // Geneate a key in the range [a,i]. 
        int value = i;          // Set the value to i.
        map.insert(make_pair(key, value));
    });

    // Print the elements in the map.
    for_each(begin(map), end(map), [](const pair<char, int>& pr) {
        wcout << L"[" << pr.first << L", " << pr.second << L"] ";
    });
}
/* Sample output:
    [e, 751] [i, 755] [a, 756] [c, 758] [g, 753] [f, 752] [b, 757] [d, 750] [h, 754]
*/

Para obtener un ejemplo que utiliza concurrent_unordered_map para realizar un mapa y reducir la operación en paralelo, vea Cómo: Realizar operaciones de asignación y reducción en paralelo.

Concurrent_unordered_map y unordered_map de diferencias entre

La clase concurrent_unordered_map es muy similar a la clase unordered_map. En los siguientes puntos se muestran las diferencias entre concurrent_unordered_map y unordered_map:

  • erase, bucket, bucket_count, y los métodos de bucket_size se denominan unsafe_erase, unsafe_bucket, unsafe_bucket_count, y unsafe_bucket_size, respectivamente. La convención de nomenclatura de unsafe_ indica que estos métodos no son simultaneidad-seguros. Para obtener más información sobre seguridad de simultaneidad, vea Operaciones Simultaneidad-seguras.

  • Las operaciones de inserción no invalidan los punteros o iteradores existentes, ni haga cambie el orden de los elementos que ya existen en el mapa. Las operaciones INSERT y la atraviesen pueden aparecer en paralelo.

  • concurrent_unordered_map admite la iteración de avance.

  • La inserción no reemplaza ni actualiza los iteradores devueltos por equal_range. Inserción puede anexar elementos desiguales al final del intervalo. Los puntos de iterador de inicio a un elemento igual.

Para ayudar a evitar el interbloqueo, cualquier método de concurrent_unordered_map mantiene un bloqueo cuando llama al asignador de memoria, las funciones hash, u otro código definido por el usuario. Además, debe asegurarse de que la función hash evaluar siempre las teclas igual al mismo valor. Las mejores funciones hash enrutan las teclas uniformemente a través del espacio del código hash.

Operaciones seguras para simultaneidad

La clase de concurrent_unordered_map habilita la inserción y las operaciones simultaneidad- seguras de elemento Access. Las operaciones de inserción no invalidan los punteros o iteradores existentes. El acceso a los iteradores y las operaciones de cruce seguro también son seguras para simultaneidad. La tabla siguiente se muestran los métodos y operadores de uso general de concurrent_unordered_map que son simultaneidad- seguros.

at

count

find

key_eq

begin

empty

get_allocator

max_size

cbegin

end

hash_function

operator[]

cend

equal_range

insert

size

Aunque el método de count se puede llamar con seguridad en paralelo de ejecutar los subprocesos, los subprocesos diferentes pueden recibir resultados diferentes si un nuevo valor se inserta simultáneamente en el contenedor.

La tabla siguiente se muestran los métodos y operadores de uso general que no son simultaneidad- seguros.

clear

max_load_factor

rehash

load_factor

operator=

swap

Además de estos métodos, cualquier método que comience con unsafe_ tampoco es simultaneidad- seguro.

[Arriba]

clase de concurrent_unordered_multimap

La clase de concurrency::concurrent_unordered_multimap es muy similar a la clase de concurrent_unordered_map salvo que permite varios valores asignar a la misma clave. También difiere de concurrent_unordered_map de las maneras siguientes:

  • El método de concurrent_unordered_multimap::insert devuelve un iterador en lugar de std::pair<iterator, bool>.

  • La clase de concurrent_unordered_multimap no proporciona operator[] ni el método de at .

El ejemplo siguiente se muestra la estructura básica para utilizar concurrent_unordered_multimap. Este ejemplo inserta teclas de caracteres en el intervalo [“a”, “i”]. concurrent_unordered_multimap permite a una clave para tener varios valores.

// unordered-multimap-structure.cpp 
// compile with: /EHsc
#include <ppl.h>
#include <concurrent_unordered_map.h>
#include <iostream>

using namespace concurrency;
using namespace std;

int wmain() 
{
    // 
    // Insert a number of items into the map in parallel.

    concurrent_unordered_multimap<char, int> map; 

    parallel_for(0, 10, [&map](int i) {
        char key = 'a' + (i%9); // Geneate a key in the range [a,i]. 
        int value = i;          // Set the value to i.
        map.insert(make_pair(key, value));
    });

    // Print the elements in the map.
    for_each(begin(map), end(map), [](const pair<char, int>& pr) {
        wcout << L"[" << pr.first << L", " << pr.second << L"] ";
    });
}
/* Sample output:
    [e, 4] [i, 8] [a, 9] [a, 0] [c, 2] [g, 6] [f, 5] [b, 1] [d, 3] [h, 7]
*/

[Arriba]

clase de concurrent_unordered_set

La clase de concurrency::concurrent_unordered_set es muy similar a la clase de concurrent_unordered_map salvo que administra valores en lugar de pares de clave y valor. La clase de concurrent_unordered_set no proporciona operator[] ni el método de at .

El ejemplo siguiente se muestra la estructura básica para utilizar concurrent_unordered_set. Este ejemplo inserta valores de caracteres en el intervalo [“a”, “i”]. Es seguro realizar las inserciones en paralelo.

// unordered-set-structure.cpp 
// compile with: /EHsc
#include <ppl.h>
#include <concurrent_unordered_set.h>
#include <iostream>

using namespace concurrency;
using namespace std;

int wmain() 
{
    // 
    // Insert a number of items into the set in parallel.

    concurrent_unordered_set<char> set; 

    parallel_for(0, 10000, [&set](int i) {
        set.insert('a' + (i%9)); // Geneate a value in the range [a,i].
    });

    // Print the elements in the set.
    for_each(begin(set), end(set), [](char c) {
        wcout << L"[" << c << L"] ";
    });
}
/* Sample output:
    [e] [i] [a] [c] [g] [f] [b] [d] [h]
*/

[Arriba]

clase de concurrent_unordered_multiset

La clase de concurrency::concurrent_unordered_multiset es muy similar a la clase de concurrent_unordered_set salvo que permite valores duplicados. También difiere de concurrent_unordered_set de las maneras siguientes:

  • El método de concurrent_unordered_multiset::insert devuelve un iterador en lugar de std::pair<iterator, bool>.

  • La clase de concurrent_unordered_multiset no proporciona operator[] ni el método de at .

El ejemplo siguiente se muestra la estructura básica para utilizar concurrent_unordered_multiset. Este ejemplo inserta valores de caracteres en el intervalo [“a”, “i”]. concurrent_unordered_multiset permite a un valor para producirse varias veces.

// unordered-set-structure.cpp 
// compile with: /EHsc
#include <ppl.h>
#include <concurrent_unordered_set.h>
#include <iostream>

using namespace concurrency;
using namespace std;

int wmain() 
{
    // 
    // Insert a number of items into the set in parallel.

    concurrent_unordered_multiset<char> set; 

    parallel_for(0, 40, [&set](int i) {
        set.insert('a' + (i%9)); // Geneate a value in the range [a,i].
    });

    // Print the elements in the set.
    for_each(begin(set), end(set), [](char c) {
        wcout << L"[" << c << L"] ";
    });
}
/* Sample output:
    [e] [e] [e] [e] [i] [i] [i] [i] [a] [a] [a] [a] [a] [c] [c] [c] [c] [c] [g] [g]
    [g] [g] [f] [f] [f] [f] [b] [b] [b] [b] [b] [d] [d] [d] [d] [d] [h] [h] [h] [h]
*/

[Arriba]

Clase combinable

La clase de concurrency::combinable proporciona reutilizable, el almacenamiento local de subprocesos que permite realizar cálculos específicos y después 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:

[Arriba]

Temas relacionados

Reference

Clase concurrent_vector

Clase concurrent_queue

concurrent_unordered_map (Clase)

concurrent_unordered_multimap (Clase)

concurrent_unordered_set (Clase)

concurrent_unordered_multiset (Clase)

Clase combinable