Параллельные контейнеры и объекты
Библиотека параллельных шаблонов (PPL) включает несколько контейнеров и объектов, которые обеспечивают потокобезопасный доступ к их элементам.
Одновременный контейнер предоставляет безопасный для параллелизма доступ к наиболее важным операциям. Здесь указатели или итераторы всегда допустимы. Это не гарантия инициализации элементов или определенного порядка обхода. Функциональные возможности этих контейнеров похожи на те, которые предоставляются стандартной библиотекой C++ . Например, класс параллелизма::concurrent_vector напоминает класс std::vector , за исключением того, что concurrent_vector
класс позволяет добавлять элементы параллельно. Используйте параллельные контейнеры, если у вас есть параллельный код, требующий доступа для чтения и записи к одному контейнеру.
Одновременный объект совместно используется между компонентами. Процесс, вычисляющий состояние параллельного объекта, создает тот же результат, что и другой процесс, который вычисляет то же состояние последовательно. Класс concurrency::combinable является одним из примеров параллельного типа объекта. Класс combinable
позволяет выполнять вычисления параллельно, а затем объединять эти вычисления в окончательный результат. Используйте параллельные объекты, если в противном случае используется механизм синхронизации, например мьютекс, для синхронизации доступа к общей переменной или ресурсу.
Разделы
В этом разделе подробно описаны следующие параллельные контейнеры и объекты.
Одновременные контейнеры:
Параллельные объекты:
Класс concurrent_vector
Класс параллелизма::concurrent_vector — это класс контейнера последовательности, который, как и класс std::vector , позволяет случайным образом получить доступ к его элементам. Класс concurrent_vector
включает операции доступа к элементам и добавления параллелизма, безопасные для параллелизма. Операции добавления не делают недействительными существующие указатели или итераторы. Итератор доступа и обхода также являются безопасными для параллелизма. Здесь указатели или итераторы всегда допустимы. Это не гарантия инициализации элементов или определенного порядка обхода.
Различия между concurrent_vector и вектором
Класс concurrent_vector
очень похож на vector
класс. Сложность операций доступа к добавлению, доступу к элементам и итератору concurrent_vector
объекта совпадает с сложностью операций доступа к объекту vector
. Ниже показано, где concurrent_vector
отличается от vector
:
Добавление, доступ к элементам, доступ к итератору итератора итератору, выполняемые в объекте
concurrent_vector
, являются безопасными для параллелизма.Элементы можно добавлять только в конец
concurrent_vector
объекта. Классconcurrent_vector
не предоставляетinsert
метод.Объект
concurrent_vector
не использует семантику перемещения при добавлении к нему.Класс
concurrent_vector
не предоставляетerase
методы илиpop_back
методы. Как и при использованииvector
, используйте метод clear для удаления всех элементов изconcurrent_vector
объекта.Класс
concurrent_vector
не сохраняет его элементы в памяти. Таким образом, класс нельзя использовать всеми способами, которые можно использоватьconcurrent_vector
в массиве. Например, для переменной с именемv
типаconcurrent_vector
выражение&v[0]+2
создает неопределенное поведение.Класс
concurrent_vector
определяет методы grow_by и grow_to_at_least . Эти методы похожи на метод изменения размера , за исключением того, что они являются безопасными для параллелизма.Объект
concurrent_vector
не перемещает его элементы при его добавлении или изменении размера. Это позволяет существующим указателям и итераторам оставаться действительными во время параллельных операций.Среда выполнения не определяет специализированную версию
concurrent_vector
типаbool
.
Операции с параллелизмом и безопасностью
Все методы, добавляющие или увеличивающие размер concurrent_vector
объекта, или доступ к элементу в concurrent_vector
объекте, являются безопасными для параллелизма. Здесь указатели или итераторы всегда допустимы. Это не гарантия инициализации элементов или определенного порядка обхода. Исключением из этого правила является resize
метод.
В следующей таблице показаны распространенные concurrent_vector
методы и операторы, которые являются безопасными для параллелизма.
Операции, которые среда выполнения обеспечивает совместимость со стандартной библиотекой C++, например, reserve
не являются безопасными для параллелизма. В следующей таблице показаны распространенные методы и операторы, которые не являются безопасными для параллелизма.
Операции, изменяющие значение существующих элементов, не являются безопасными для параллелизма. Используйте объект синхронизации, например объект reader_writer_lock для синхронизации параллельных операций чтения и записи с тем же элементом данных. Дополнительные сведения о объектах синхронизации см. в разделе "Структуры данных синхронизации".
При преобразовании существующего кода, используемого vector
для использования concurrent_vector
, одновременные операции могут привести к изменению поведения приложения. Например, рассмотрим следующую программу, которая параллельно выполняет две задачи в объекте concurrent_vector
. Первая задача добавляет дополнительные элементы к объекту concurrent_vector
. Вторая задача вычисляет сумму всех элементов в одном объекте.
// 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;
}
);
}
Несмотря на end
то, что метод является безопасным, одновременный вызов метода push_back приводит к изменению значения, возвращаемого путем end
изменения. Количество элементов, которые проходит итератор, является неопределенным. Таким образом, эта программа может создавать разные результаты при каждом запуске. Если тип элемента не является тривиальным, это может быть состояние гонки между push_back
и end
вызовами. Метод end
может возвращать выделенный элемент, но не полностью инициализирован.
Безопасность исключений
Если операция роста или назначения вызывает исключение, состояние concurrent_vector
объекта становится недействительным. Поведение concurrent_vector
объекта, который находится в недопустимом состоянии, не определено, если не указано в противном случае. Однако деструктор всегда освобождает память, выделенную объектом, даже если объект находится в недопустимом состоянии.
Тип данных элементов T
вектора должен соответствовать следующим требованиям. В противном случае поведение concurrent_vector
класса не определено.
Деструктор не должен вызываться.
Если вызывается конструктор по умолчанию или копирование, деструктор не должен быть объявлен с помощью ключевого
virtual
слова, и он должен работать правильно с нулевой инициализируемой памятью.
[В начало]
Класс concurrent_queue
Класс параллелизма::concurrent_queue , как класс std::queue , позволяет получить доступ к его внешним и задним элементам. Класс concurrent_queue
включает в себя операции с параллелизмом, безопасные для очереди и отмены. Здесь указатели или итераторы всегда допустимы. Это не гарантия инициализации элементов или определенного порядка обхода. Класс concurrent_queue
также предоставляет поддержку итератора, которая не является безопасной для параллелизма.
Различия между concurrent_queue и очередью
Класс concurrent_queue
очень похож на queue
класс. Ниже показано, где concurrent_queue
отличается от queue
:
Операции enqueue и dequeue для
concurrent_queue
объекта являются безопасными для параллелизма.Класс
concurrent_queue
предоставляет поддержку итератора, которая не является безопасной для параллелизма.Класс
concurrent_queue
не предоставляетfront
методы илиpop
методы. Классconcurrent_queue
заменяет эти методы путем определения метода try_pop .Класс
concurrent_queue
не предоставляетback
метод. Поэтому вы не можете ссылаться на конец очереди.Класс
concurrent_queue
предоставляет метод unsafe_size вместоsize
метода. Методunsafe_size
не является безопасным для параллелизма.
Операции с параллелизмом и безопасностью
Все методы, которые вложены в объект или удаляются из concurrent_queue
объекта, являются безопасными для параллелизма. Здесь указатели или итераторы всегда допустимы. Это не гарантия инициализации элементов или определенного порядка обхода.
В следующей таблице показаны распространенные concurrent_queue
методы и операторы, которые являются безопасными для параллелизма.
empty
Хотя метод является параллелизмом безопасным, одновременная операция может привести к росту или сокращению очереди перед возвратом empty
метода.
В следующей таблице показаны распространенные методы и операторы, которые не являются безопасными для параллелизма.
Поддержка итератора
Предоставляет concurrent_queue
итераторы, которые не являются безопасными для параллелизма. Мы рекомендуем использовать эти итераторы только для отладки.
Итератор concurrent_queue
проходит элементы только в направлении вперед. В следующей таблице показаны операторы, поддерживаемые каждым итератором.
Operator | Description |
---|---|
operator++ |
Переходит к следующему элементу в очереди. Этот оператор перегружен для предоставления семантики предварительного и последующего увеличения. |
operator* |
Извлекает ссылку на текущий элемент. |
operator-> |
Извлекает указатель на текущий элемент. |
[В начало]
Класс concurrent_unordered_map
Класс параллелизма::concurrent_unordered_map — это ассоциативный класс контейнера, который, как и класс std::unordered_map, управляет последовательностью элементов типа std::p air<const Key, Ty>. Подумайте о неупорядоченной карте как словаре, в которую можно добавить пару "ключ и значение" или искать значение по ключу. Этот класс полезен при наличии нескольких потоков или задач, которые должны одновременно обращаться к общему контейнеру, вставлять в него или обновлять его.
В следующем примере показана базовая структура для использования concurrent_unordered_map
. В этом примере вставляется клавиши символов в диапазон ['a', 'i']. Так как порядок операций не определен, окончательное значение для каждого ключа также не определено. Однако это безопасно для параллельного выполнения вставок.
// 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]
*/
Пример, который используется concurrent_unordered_map
для параллельного выполнения операции сопоставления и уменьшения, см. в статье "Практическое руководство. Выполнение операций сопоставления и уменьшения числа операций параллельно".
Различия между concurrent_unordered_map и unordered_map
Класс concurrent_unordered_map
очень похож на unordered_map
класс. Ниже показано, где concurrent_unordered_map
отличается от unordered_map
:
bucket
bucket_count
Именаerase
, иbucket_size
методы имеют именаunsafe_erase
,unsafe_bucket_count
unsafe_bucket
иunsafe_bucket_size
соответственно. Соглашениеunsafe_
об именовании указывает, что эти методы не являются безопасными для параллелизма. Дополнительные сведения о безопасности параллелизма см. в разделе "Безопасные операции параллелизма".Операции вставки не делают недействительными существующие указатели или итераторы, а также не изменяют порядок элементов, которые уже существуют в карте. Операции вставки и обхода могут выполняться одновременно.
concurrent_unordered_map
поддерживает только итерацию пересылки.Вставка не отменяет или не обновляет итераторы, возвращаемые
equal_range
. Вставка может добавлять неравные элементы в конец диапазона. Начальный итератор указывает на равный элемент.
Чтобы избежать взаимоблокировки, метод concurrent_unordered_map
блокировки не содержит блокировку при вызове распределителя памяти, хэш-функций или другого пользовательского кода. Кроме того, необходимо убедиться, что хэш-функция всегда вычисляет равные ключи с тем же значением. Лучшие хэш-функции равномерно распределяют ключи между хэш-кодом.
Операции с параллелизмом и безопасностью
Класс concurrent_unordered_map
включает операции вставки и доступа к элементам, безопасные для параллелизма. Операции вставки не отменяют существующие указатели или итераторы. Итератор доступа и обхода также являются безопасными для параллелизма. Здесь указатели или итераторы всегда допустимы. Это не гарантия инициализации элементов или определенного порядка обхода. В следующей таблице показаны часто используемые concurrent_unordered_map
методы и операторы, которые являются безопасными для параллелизма.
Несмотря на count
то, что метод можно вызывать безопасно из одновременно выполняющихся потоков, разные потоки могут получать разные результаты, если новое значение одновременно вставляется в контейнер.
В следующей таблице показаны часто используемые методы и операторы, которые не являются безопасными для параллелизма.
Помимо этих методов, любой метод, начинающийся с unsafe_
, также не является безопасным для параллелизма.
[В начало]
Класс concurrent_unordered_multimap
Класс параллелизма::concurrent_unordered_multimap очень похож на concurrent_unordered_map
класс, за исключением того, что он позволяет сопоставить несколько значений с одним ключом. Он также отличается от concurrent_unordered_map
следующих способов:
Метод concurrent_unordered_multimap::insert возвращает итератор вместо
std::pair<iterator, bool>
.Класс
concurrent_unordered_multimap
не предоставляет иat
не предоставляетoperator[]
метод.
В следующем примере показана базовая структура для использования concurrent_unordered_multimap
. В этом примере вставляется клавиши символов в диапазон ['a', 'i']. concurrent_unordered_multimap
позволяет ключу иметь несколько значений.
// 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]
*/
[В начало]
Класс concurrent_unordered_set
Класс параллелизма::concurrent_unordered_set очень похож на concurrent_unordered_map
класс, за исключением того, что он управляет значениями вместо пар "ключ и значение". Класс concurrent_unordered_set
не предоставляет и at
не предоставляет operator[]
метод.
В следующем примере показана базовая структура для использования concurrent_unordered_set
. В этом примере вставляется символьные значения в диапазон ['a', 'i']. Это безопасно для параллельного выполнения вставок.
// 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]
*/
[В начало]
Класс concurrent_unordered_multiset
Класс параллелизма::concurrent_unordered_multiset очень похож на concurrent_unordered_set
класс, за исключением того, что он позволяет повторять значения. Он также отличается от concurrent_unordered_set
следующих способов:
Метод concurrent_unordered_multiset::insert возвращает итератор вместо
std::pair<iterator, bool>
.Класс
concurrent_unordered_multiset
не предоставляет иat
не предоставляетoperator[]
метод.
В следующем примере показана базовая структура для использования concurrent_unordered_multiset
. В этом примере вставляется символьные значения в диапазон ['a', 'i']. concurrent_unordered_multiset
позволяет несколько раз возникать значение.
// 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]
*/
[В начало]
Класс combinable
Класс concurrency::combinable предоставляет повторное использование, локальное хранилище потоков, которое позволяет выполнять точные вычисления, а затем объединять эти вычисления в окончательный результат. Объект combinable
можно рассматривать как переменную уменьшения.
Класс combinable
полезен, если у вас есть ресурс, общий для нескольких потоков или задач. Класс combinable
помогает устранить общее состояние, предоставив доступ к общим ресурсам без блокировки. Таким образом, этот класс предоставляет альтернативу использованию механизма синхронизации, например мьютекса, для синхронизации доступа к общим данным из нескольких потоков.
Методы и компоненты
В следующей combinable
таблице показаны некоторые важные методы класса. Дополнительные сведения обо всех методах класса см. в combinable
объединяемом классе.
Метод | Description |
---|---|
local | Извлекает ссылку на локальную переменную, связанную с текущим контекстом потока. |
пусто | Удаляет все локальные переменные потока из combinable объекта. |
combine combine_each |
Использует предоставленную функцию объединения для создания окончательного значения из набора всех вычислений, локальных потоков. |
Класс combinable
— это класс шаблона, параметризованный в окончательном объединенном результате. При вызове конструктора T
по умолчанию тип параметра шаблона должен иметь конструктор по умолчанию и конструктор копирования. T
Если тип параметра шаблона не имеет конструктора по умолчанию, вызовите перегруженную версию конструктора, которая принимает функцию инициализации в качестве параметра.
После вызова методов объединения или combine_each можно хранить дополнительные данные в combinable
объекте. Вы также можете вызывать combine
методы и combine_each
несколько раз. Если локальное значение в объекте combinable
не изменяется, combine
combine_each
методы создают одинаковый результат при каждом вызове.
Примеры
Примеры использования combinable
класса см. в следующих разделах:
Практическое руководство. Использование класса combinable для повышения производительности
Практическое руководство. Использование класса combinable для комбинирования наборов
[В начало]
См. также
Практическое руководство. Использование параллельных контейнеров для повышения эффективности
Показывает, как использовать параллельные контейнеры для эффективного хранения и доступа к данным параллельно.
Практическое руководство. Использование класса combinable для повышения производительности
Показывает, как использовать combinable
класс для устранения общего состояния и тем самым повысить производительность.
Практическое руководство. Использование класса combinable для комбинирования наборов
Показывает, как использовать combine
функцию для слияния локальных наборов данных потока.
Библиотека параллельных шаблонов
Описывает PPL, которая предоставляет императивную модель программирования, которая способствует масштабируемости и простоте использования для разработки параллельных приложений.
Справочные материалы
Класс concurrent_unordered_map
Класс concurrent_unordered_multimap
Класс concurrent_unordered_set