Contêineres e objetos em paralelo
A PPL (Biblioteca de Padrões de Paralelismo) inclui vários contêineres e objetos que fornecem acesso thread-safe aos elementos.
Um contêiner simultâneo fornece acesso concurrency-safe para as operações mais importantes. Aqui, concurrency-safe significa que os ponteiros ou iteradores são sempre válidos. Não é uma garantia de inicialização do elemento ou de uma ordem de passagem específica. A funcionalidade desses contêineres é semelhante às fornecidas pela Biblioteca Padrão C++. Por exemplo, a classe concurrency::concurrent_vector é semelhante à classe std::vector, exceto pelo fato de que a classe concurrent_vector
permite acrescentar elementos paralelamente. Use contêineres simultâneos quando tiver um código paralelo que exija acesso de leitura e gravação ao mesmo contêiner.
Um objeto simultâneo é compartilhado simultaneamente entre os componentes. Um processo que calcula o estado de um objeto simultâneo paralelamente produz o mesmo resultado que outro processo que calcula o mesmo estado serialmente. A classe concurrency::combinable é um exemplo de um tipo de objeto simultâneo. A classe combinable
permite que você execute cálculos paralelamente e depois combine esses cálculos em um resultado final. Use objetos simultâneos quando você usaria um mecanismo de sincronização, por exemplo, um mutex, para sincronizar o acesso a uma variável ou recurso compartilhado.
Seções
Este tópico descreve os seguintes contêineres e objetos paralelos detalhadamente.
Contêineres simultâneos:
Objetos simultâneos:
Classe concurrent_vector
A classe concurrency::concurrent_vector é uma classe de contêiner de sequência que, assim como a classe std::vector, permite acessar aleatoriamente os elementos. A classe concurrent_vector
permite operações de acesso ao elemento e acréscimo concurrency-safe. As operações de acréscimo não invalidam os ponteiros ou iteradores existentes. As operações de passagem e acesso do iterador também são concurrency-safe. Aqui, concurrency-safe significa que os ponteiros ou iteradores são sempre válidos. Não é uma garantia de inicialização do elemento ou de uma ordem de passagem específica.
Diferenças entre concurrent_vector e vector
A classe concurrent_vector
é muito semelhante à classe vector
. A complexidade das operações de acréscimo, acesso ao elemento e acesso ao iterador em um objeto concurrent_vector
é a mesma de um objeto vector
. Os seguintes pontos ilustram onde concurrent_vector
é diferente de vector
:
As operações de acréscimo, acesso ao elemento, acesso ao iterador e passagem do iterador em um objeto
concurrent_vector
são concurrency-safe.Você pode adicionar elementos somente ao final de um objeto
concurrent_vector
. A classeconcurrent_vector
não fornece o métodoinsert
.Um objeto
concurrent_vector
não usa a semântica de transferência de recursos, quando você acrescenta a ele.A classe
concurrent_vector
não fornece o métodoerase
oupop_back
. Assim como acontece comvector
, use o método clear para remover todos os elementos de um objetoconcurrent_vector
.A classe
concurrent_vector
não armazena os elementos contiguamente na memória. Portanto, você não pode usar a classeconcurrent_vector
de todas as maneiras que pode usar uma matriz. Por exemplo, para uma variável chamadav
do tipoconcurrent_vector
, a expressão&v[0]+2
produz um comportamento indefinido.A classe
concurrent_vector
define os métodos grow_by e grow_to_at_least. Esses métodos são semelhantes ao método resize, exceto pelo fato de que são concurrency-safe.Um objeto
concurrent_vector
não realoca os elementos quando você acrescenta a ele ou o redimensiona. Isso permite que ponteiros e iteradores existentes permaneçam válidos durante operações simultâneas.O runtime não define uma versão especializada do
concurrent_vector
para o tipobool
.
Operações concurrency-safe
Todos os métodos que acrescentam ou aumentam o tamanho de um objeto concurrent_vector
ou acessam um elemento em um objeto concurrent_vector
são concurrency-safe. Aqui, concurrency-safe significa que os ponteiros ou iteradores são sempre válidos. Não é uma garantia de inicialização do elemento ou de uma ordem de passagem específica. A exceção a essa regra é o método resize
.
A tabela a seguir mostra os métodos e operadores comuns concurrent_vector
que são concurrency-safe.
As operações que o runtime fornece para compatibilidade com a Biblioteca Padrão C++, por exemplo, reserve
, não são concurrency-safe. A tabela a seguir mostra os métodos e operadores comuns que não são concurrency-safe.
As operações que modificam o valor dos elementos existentes não são concurrency-safe. Use um objeto de sincronização, como um objeto reader_writer_lock, para sincronizar operações simultâneas de leitura e gravação com o mesmo elemento de dados. Para obter mais informações sobre objetos de sincronização, confira Estruturas de Dados de Sincronização.
Quando você converte o código existente que usa vector
para usar concurrent_vector
, as operações simultâneas podem fazer com que o comportamento do aplicativo seja alterado. Por exemplo, considere o programa a seguir que executa simultaneamente duas tarefas em um objeto concurrent_vector
. A primeira tarefa acrescenta elementos adicionais a um objeto concurrent_vector
. A segunda tarefa calcula a soma de todos os elementos no mesmo 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;
}
);
}
Embora o método end
seja concurrency-safe, uma chamada simultânea ao método push_back faz com que o valor retornado por end
seja alterado. O número de elementos que o iterador passa é indeterminado. Portanto, esse programa pode produzir um resultado diferente cada vez que é executado. Quando o tipo de elemento não é trivial, é possível que uma condição de corrida exista entre chamadas a push_back
e end
. O método end
pode retornar um elemento alocado, mas não totalmente inicializado.
Segurança de Exceção
Se uma operação de crescimento ou atribuição gerar uma exceção, o estado do objeto concurrent_vector
se tornará inválido. O comportamento de um objeto concurrent_vector
que está em um estado inválido é indefinido, a menos que indicado de outra forma. No entanto, o destruidor sempre libera a memória alocada pelo objeto, mesmo que o objeto esteja em um estado inválido.
O tipo de dados dos elementos vector, T
, deve atender aos requisitos a seguir. Caso contrário, o comportamento da classe concurrent_vector
será indefinido.
O destruidor não deve ser gerado.
Se o construtor padrão ou de cópia for gerado, o destruidor não deve ser declarado usando a palavra-chave
virtual
e deve funcionar corretamente com a memória inicializada em zero.
Classe concurrent_queue
A classe concurrency::concurrent_queue, assim como a classe std::queue, permite acessar os elementos front e back. A classe concurrent_queue
habilita operações de enfileiramento e remoção da fila concurrency-safe. Aqui, concurrency-safe significa que os ponteiros ou iteradores são sempre válidos. Não é uma garantia de inicialização do elemento ou de uma ordem de passagem específica. A classe concurrent_queue
também fornece suporte ao iterador que não é concurrency-safe.
Diferenças entre concurrent_queue e queue
A classe concurrent_queue
é muito semelhante à classe queue
. Os seguintes pontos ilustram onde concurrent_queue
é diferente de queue
:
As operações de enfileiramento e remoção da fila em um objeto
concurrent_queue
são concurrency-safe.A classe
concurrent_queue
fornece suporte ao iterador que não é concurrency-safe.A classe
concurrent_queue
não fornece o métodofront
oupop
. A classeconcurrent_queue
substitui esses métodos, definindo o método try_pop.A classe
concurrent_queue
não fornece o métodoback
. Portanto, você não pode referenciar o fim da fila.A classe
concurrent_queue
fornece o método unsafe_size, em vez do métodosize
. O métodounsafe_size
não é concurrency-safe.
Operações concurrency-safe
Todos os métodos de enfileiramento ou remoção da fila de um objeto concurrent_queue
são concurrency-safe. Aqui, concurrency-safe significa que os ponteiros ou iteradores são sempre válidos. Não é uma garantia de inicialização do elemento ou de uma ordem de passagem específica.
A tabela a seguir mostra os métodos e operadores comuns concurrent_queue
que são concurrency-safe.
Embora o método empty
seja concurrency-safe, uma operação simultânea pode fazer com que a fila aumente ou diminua antes que o método empty
retorne.
A tabela a seguir mostra os métodos e operadores comuns que não são concurrency-safe.
Suporte ao Iterador
O concurrent_queue
fornece iteradores que não são concurrency-safe. Recomendamos que você use esses iteradores somente para depuração.
Um iterador concurrent_queue
passa os elementos apenas na direção para frente. A tabela a seguir mostra os operadores aos quais cada iterador dá suporte.
Operador | Descrição |
---|---|
operator++ |
Avança para o próximo item na fila. Esse operador é sobrecarregado para fornecer as semântica de pré-incremento e pós-incremento. |
operator* |
Recupera uma referência ao item atual. |
operator-> |
Recupera um ponteiro para o item atual. |
Classe concurrent_unordered_map
A classe concurrency::concurrent_unordered_map é uma classe de contêiner associativa que, assim como a classe std::unordered_map, controla uma sequência de tamanho variado de elementos do tipo std::p air<const Key, Ty>. Pense em um mapa não ordenado como um dicionário para o qual você pode adicionar um par de chave e valor ou pesquisar um valor por chave. Essa classe é útil quando você tem vários threads ou tarefas que precisam acessar simultaneamente um contêiner compartilhado, inserir nele ou atualizá-lo.
O exemplo a seguir mostra a estrutura básica para usar concurrent_unordered_map
. Este exemplo insere chaves de caractere no intervalo ['a', 'i']. Como a ordem das operações é indeterminada, o valor final de cada chave também é indeterminado. No entanto, é seguro executar as inserções paralelamente.
// 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 obter outro exemplo que usa concurrent_unordered_map
para executar uma operação de mapeamento e redução paralelamente, confira Instruções: executar operações de mapeamento e redução paralelamente.
Diferenças entre concurrent_unordered_map e unordered_map
A classe concurrent_unordered_map
é muito semelhante à classe unordered_map
. Os seguintes pontos ilustram onde concurrent_unordered_map
é diferente de unordered_map
:
Os métodos
erase
,bucket
,bucket_count
ebucket_size
são chamados deunsafe_erase
,unsafe_bucket
,unsafe_bucket_count
eunsafe_bucket_size
, respectivamente. A convenção de nomenclaturaunsafe_
indica que esses métodos não são concurrency-safe. Para obter mais informações sobre concurrency-safe, confira Operações concurrency-safe.As operações de inserção não invalidam os ponteiros ou iteradores existentes, nem alteram a ordem dos itens que já existem no mapa. As operações de inserção e passagem podem ocorrer simultaneamente.
concurrent_unordered_map
dá suporte somente à iteração para frente.A inserção não invalida nem atualiza os iteradores retornados por
equal_range
. A inserção pode acrescentar itens desiguais ao final do intervalo. O iterador inicial aponta para um item igual.
Para ajudar a evitar deadlock, nenhum método de concurrent_unordered_map
mantém um bloqueio quando chama o alocador de memória, as funções de hash ou outro código definido pelo usuário. Além disso, você deve garantir que a função de hash sempre avalie chaves iguais ao mesmo valor. As melhores funções de hash distribuem as chaves uniformemente no espaço de código de hash.
Operações concurrency-safe
A classe concurrent_unordered_map
permite operações de acesso ao elemento e inserção concurrency-safe. As operações de inserção não invalidam os ponteiros ou iteradores existentes. As operações de passagem e acesso do iterador também são concurrency-safe. Aqui, concurrency-safe significa que os ponteiros ou iteradores são sempre válidos. Não é uma garantia de inicialização do elemento ou de uma ordem de passagem específica. A tabela a seguir mostra os métodos e operadores concurrent_unordered_map
usados normalmente que são concurrency-safe.
Embora o método count
possa ser chamado com segurança a partir de threads executados simultaneamente, threads diferentes podem receber resultados diferentes se um novo valor for inserido simultaneamente no contêiner.
A tabela a seguir mostra os métodos e operadores usados normalmente que não são concurrency-safe.
Além desses métodos, qualquer método que comece com unsafe_
também não é concurrency-safe.
Classe concurrent_unordered_multimap
A classe concurrency::concurrent_unordered_multimap é muito semelhante à classe concurrent_unordered_map
, exceto pelo fato de permitir que vários valores sejam mapeados para a mesma chave. Ela também é diferente de concurrent_unordered_map
nestes aspectos:
O método concurrent_unordered_multimap::insert retorna um iterador, em vez de
std::pair<iterator, bool>
.A classe
concurrent_unordered_multimap
não fornece o métodooperator[]
nemat
.
O exemplo a seguir mostra a estrutura básica para usar concurrent_unordered_multimap
. Este exemplo insere chaves de caractere no intervalo ['a', 'i']. concurrent_unordered_multimap
permite que uma chave tenha vários 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]
*/
Classe concurrent_unordered_set
A classe concurrency::concurrent_unordered_set é muito semelhante à classe concurrent_unordered_map
, exceto pelo fato de gerenciar valores, em vez de pares de chave e valor. A classe concurrent_unordered_set
não fornece o método operator[]
nem at
.
O exemplo a seguir mostra a estrutura básica para usar concurrent_unordered_set
. Este exemplo insere valores de caractere no intervalo ['a', 'i']. É seguro executar as inserções paralelamente.
// 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]
*/
Classe concurrent_unordered_multiset
A classe concurrency::concurrent_unordered_multiset é muito semelhante à classe concurrent_unordered_set
, exceto pelo fato de permitir valores duplicados. Ela também é diferente de concurrent_unordered_set
nestes aspectos:
O método concurrent_unordered_multiset::insert retorna um iterador, em vez de
std::pair<iterator, bool>
.A classe
concurrent_unordered_multiset
não fornece o métodooperator[]
nemat
.
O exemplo a seguir mostra a estrutura básica para usar concurrent_unordered_multiset
. Este exemplo insere valores de caractere no intervalo ['a', 'i']. concurrent_unordered_multiset
permite que um valor ocorra várias vezes.
// 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]
*/
Classe combinable
A classe concurrency::combinablefornece armazenamento thread-local reutilizável que permite executar cálculos refinados e mesclar esses cálculos em um resultado final. É possível pensar em um objeto combinable
como uma variável de redução.
A classe combinable
é útil quando você tem um recurso compartilhado entre vários threads ou tarefas. A classe combinable
ajuda você a eliminar o estado compartilhado, fornecendo acesso aos recursos compartilhados de uma maneira livre de bloqueios. Portanto, essa classe fornece uma alternativa ao uso de um mecanismo de sincronização, por exemplo, um mutex, para sincronizar o acesso aos dados compartilhados a partir de vários threads.
Métodos e Recursos
A tabela a seguir mostra alguns dos métodos importantes da classe combinable
. Para obter mais informações sobre os métodos de classe combinable
, confira Classe combinable.
Método | Descrição |
---|---|
local | Recupera uma referência à variável local associada ao contexto de thread atual. |
clear | Remove todas as variáveis thread-local do objeto combinable . |
combine combine_each |
Usa a função de combinação fornecida para gerar um valor final a partir do conjunto de todos os cálculos thread-local. |
A classe combinable
é uma classe de modelo parametrizada no resultado mesclado final. Se você chamar o construtor padrão, o tipo de parâmetro de modelo T
deve ter um construtor padrão e um construtor de cópia. Se o tipo de parâmetro de modelo T
não tiver um construtor padrão, chame a versão sobrecarregada do construtor que usa uma função de inicialização como parâmetro.
Você pode armazenar dados adicionais em um objeto combinable
, depois de chamar os métodos combine ou combine_each. Você também pode chamar os métodos e combine
os combine_each
métodos várias vezes. Se nenhum valor local em um objeto combinable
for alterado, os métodos combine
e combine_each
produzirão o mesmo resultado sempre que forem chamados.
Exemplos
Para obter exemplos sobre como usar a classe combinable
, confira os tópicos a seguir:
Tópicos Relacionados
Como usar contêineres em paralelo para aumentar a eficiência
Mostra como usar contêineres paralelos para armazenar e acessar dados paralelamente com eficiência.
Como usar combinável para melhorar o desempenho
Mostra como usar a classe combinable
para eliminar o estado compartilhado e, assim, melhorar o desempenho.
Como usar combinável para combinar conjuntos
Mostra como usar uma função combine
para mesclar conjuntos de dados thread-local.
Biblioteca de padrões paralelos (PPL)
Descreve o PPL, que fornece um modelo de programação imperativo que promove a escalabilidade e a facilidade de uso para o desenvolvimento de aplicativos simultâneos.
Referência
Classe concurrent_unordered_map
Classe concurrent_unordered_multimap
Classe concurrent_unordered_set