Параллельные контейнеры и объекты

Библиотека параллельных шаблонов (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 может возвращать выделенный элемент, но не полностью инициализирован.

Исключение Сейф ty

Если операция роста или назначения вызывает исключение, состояние 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:

  • bucketbucket_countИмена erase, и bucket_size методы имеют имена unsafe_erase, unsafe_bucket_countunsafe_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 объединяемом классе.

Способ Описание
local Извлекает ссылку на локальную переменную, связанную с текущим контекстом потока.
пусто Удаляет все локальные переменные потока из combinable объекта.
combine

combine_each
Использует предоставленную функцию объединения для создания окончательного значения из набора всех вычислений, локальных потоков.

Класс combinable — это класс шаблона, параметризованный в окончательном объединенном результате. При вызове конструктора T по умолчанию тип параметра шаблона должен иметь конструктор по умолчанию и конструктор копирования. T Если тип параметра шаблона не имеет конструктора по умолчанию, вызовите перегруженную версию конструктора, которая принимает функцию инициализации в качестве параметра.

После вызова методов объединения или combine_each можно хранить дополнительные данные в combinable объекте. Вы также можете вызывать combine методы и combine_each несколько раз. Если локальное значение в объекте combinable не изменяется, combinecombine_each методы создают одинаковый результат при каждом вызове.

Примеры

Примеры использования combinable класса см. в следующих разделах:

[В начало]

Практическое руководство. Использование параллельных контейнеров для повышения эффективности
Показывает, как использовать параллельные контейнеры для эффективного хранения и доступа к данным параллельно.

Практическое руководство. Использование класса combinable для повышения производительности
Показывает, как использовать combinable класс для устранения общего состояния и тем самым повысить производительность.

Практическое руководство. Использование класса combinable для комбинирования наборов
Показывает, как использовать combine функцию для слияния локальных наборов данных потока.

Библиотека параллельных шаблонов
Описывает PPL, которая предоставляет императивную модель программирования, которая способствует масштабируемости и простоте использования для разработки параллельных приложений.

Ссылка

Класс concurrent_vector

Класс concurrent_queue

Класс concurrent_unordered_map

Класс concurrent_unordered_multimap

Класс concurrent_unordered_set

Класс concurrent_unordered_multiset

Класс combinable