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

Библиотека параллельных шаблонов (PPL) включает несколько контейнеров и объектов, которые обеспечивают потокобезопасный доступ к их элементам.

Параллельный контейнер обеспечивает потокобезопасный доступ к наиболее важным операциям. В данном случае, безопасность с параллелизмом означает, что указатели или итераторы всегда действительны. Не гарантируется инициализация элементов или определенный порядок обхода. Функциональные возможности этих контейнеров похожи на функции, предоставляемые стандартной библиотекой C++. Например, класс Concurrency:: concurrent_vector похож на класс std:: Vector , за исключением того, что concurrent_vector класс позволяет добавлять элементы параллельно. Одновременно используйте параллельные контейнеры, для которых требуется доступ на чтение и запись к одному и тому же контейнеру.

Параллельный объект совместно используется компонентами. Процесс, который вычисляет состояние параллельного объекта в Parallel, дает тот же результат, что и другой процесс, который последовательно вычисляет одно и то же состояние. Класс Concurrency:: combinable является одним из примеров параллельного типа объекта. combinableКласс позволяет выполнять вычисления параллельно, а затем объединять эти вычисления в окончательный результат. Используйте параллельные объекты, если в противном случае использовать механизм синхронизации, например мьютекс, для синхронизации доступа к общей переменной или ресурсу.

Священ

В этом разделе подробно описаны следующие параллельные контейнеры и объекты.

Параллельные контейнеры:

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

Класс concurrent_vector

Класс Concurrency:: concurrent_vector является классом-контейнером последовательности, который, как и класс std:: Vector , обеспечивает случайный доступ к его элементам. concurrent_vectorКласс позволяет выполнять операции добавления и доступа к элементам в режиме параллелизма. Операции добавления не делают недействительными существующие указатели или итераторы. Операции доступа и обхода итератора также являются надежными в режиме параллелизма. В данном случае, безопасность с параллелизмом означает, что указатели или итераторы всегда действительны. Не гарантируется инициализация элементов или определенный порядок обхода.

Различия между concurrent_vector и 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 .

Concurrency-Safe операции

Все методы, которые добавляют или увеличивают размер 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 объекта в недопустимом состоянии не определено, если не указано иное. Однако деструктор всегда освобождает память, выделенную объектом, даже если объект находится в недопустимом состоянии.

Тип данных элементов Vector, T должен соответствовать следующим требованиям. В противном случае поведение concurrent_vector класса не определено.

  • Деструктор не должен вызывать исключение.

  • Если конструктор по умолчанию или экземпляр создает исключение, деструктор не должен быть объявлен с помощью virtual ключевого слова и должен правильно работать с памятью, инициализированной нулем.

[Top]

Класс concurrent_queue

Класс Concurrency:: concurrent_queue , как и класс std:: Queue , позволяет получить доступ к элементам Front и back. concurrent_queueКласс включает постановку в очередь с параллелизмом и операции вывода из очереди. В данном случае, безопасность с параллелизмом означает, что указатели или итераторы всегда действительны. Не гарантируется инициализация элементов или определенный порядок обхода. concurrent_queueКласс также предоставляет поддержку итератора, которая не является надежной в режиме параллелизма.

Различия между concurrent_queue и очередью

Класс будет похож на класс concurrent_queue queue . Следующие моменты иллюстрируют, где concurrent_queue отличия от queue :

  • Операции постановки в очередь и выработки из очереди для concurrent_queue объекта являются надежными в режиме параллелизма.

  • concurrent_queueКласс обеспечивает поддержку итератора, который не является типобезопасным.

  • concurrent_queueКласс не предоставляет front pop методы или. concurrent_queueКласс заменяет эти методы, определяя метод try_pop .

  • concurrent_queueКласс не предоставляет back метод. Таким образом, нельзя ссылаться на конец очереди.

  • concurrent_queueКласс предоставляет unsafe_size метод, а не size метод. unsafe_sizeМетод не является типобезопасным.

Concurrency-Safe операции

Все методы, которые помещаются в очередь или concurrent_queue удаляются из очереди объекта, являются типобезопасными в режиме параллелизма. В данном случае, безопасность с параллелизмом означает, что указатели или итераторы всегда действительны. Не гарантируется инициализация элементов или определенный порядок обхода.

В следующей таблице показаны общие concurrent_queue методы и операторы, которые являются типобезопасными.

Хотя empty метод является типобезопасным, параллельная операция может привести к увеличению или уменьшению очереди перед empty возвратом метода.

В следующей таблице показаны общие методы и операторы, которые не являются типобезопасными в режиме параллелизма.

Поддержка итераторов

concurrent_queueПредоставляет итераторы, которые не являются надежными в режиме параллелизма. Рекомендуется использовать эти итераторы только для отладки.

concurrent_queueИтератор проходит по элементам только в прямом направлении. В следующей таблице показаны операторы, которые поддерживаются каждым итератором.

Оператор Описание
operator++ Переходит к следующему элементу в очереди. Этот оператор перегружен, чтобы обеспечить семантику до и после инкремента.
operator* Извлекает ссылку на текущий элемент.
operator-> Извлекает указатель на текущий элемент.

[Top]

Класс concurrent_unordered_map

Класс Concurrency:: 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 для параллельного выполнения операции Map и reduce см. в разделе как выполнять операции Map и reduce параллельно.

Различия между concurrent_unordered_map и unordered_map

Класс будет похож на класс concurrent_unordered_map unordered_map . Следующие моменты иллюстрируют, где concurrent_unordered_map отличия от unordered_map :

  • eraseМетоды, bucket , и bucket_count bucket_size имеют имена,, unsafe_erase unsafe_bucket unsafe_bucket_count и unsafe_bucket_size соответственно. unsafe_Соглашение об именовании указывает, что эти методы не являются типобезопасными. Дополнительные сведения о безопасности параллелизма см. в разделе операции с параллелизмом.

  • Операции INSERT не делают недействительными существующие указатели или итераторы и не изменяют порядок элементов, которые уже существуют на карте. Операции вставки и обхода могут выполняться параллельно.

  • concurrent_unordered_map поддерживает только прямую итерацию.

  • Вставка не делает недействительными или не обновляет итераторы, возвращаемые equal_range . Вставка может добавлять неравные элементы в конец диапазона. Итератор begin указывает на равный элемент.

Чтобы избежать взаимоблокировки, метод concurrent_unordered_map удерживает блокировку при вызове распределителя памяти, хэш-функций или другого определяемого пользователем кода. Кроме того, необходимо убедиться, что хэш-функция всегда вычисляет равные ключи одинаковым значением. Лучшие хэш-функции распределяют ключи равномерно через пространство хэш-кода.

Concurrency-Safe операции

concurrent_unordered_mapКласс позволяет выполнять операции INSERT и доступа к элементам с параллелизмом. Операции вставки не делают недействительными существующие указатели или итераторы. Операции доступа и обхода итератора также являются надежными в режиме параллелизма. В данном случае, безопасность с параллелизмом означает, что указатели или итераторы всегда действительны. Не гарантируется инициализация элементов или определенный порядок обхода. В следующей таблице показаны часто используемые concurrent_unordered_map методы и операторы, которые являются типобезопасными.

Хотя count метод может быть вызван безопасно из параллельно выполняющихся потоков, различные потоки могут получать разные результаты, если новое значение одновременно вставляется в контейнер.

В следующей таблице показаны часто используемые методы и операторы, которые не являются типобезопасными.

Помимо этих методов, любой метод, начинающийся с, unsafe_ также не является типобезопасным.

[Top]

Класс concurrent_unordered_multimap

Класс Concurrency:: concurrent_unordered_multimap похож на concurrent_unordered_map класс, за исключением того, что он позволяет сопоставлять несколько значений с одним и тем же ключом. Он также отличается от concurrent_unordered_map описанных ниже способов.

  • Метод concurrent_unordered_multimap:: INSERT возвращает итератор, а не std::pair<iterator, bool> .

  • concurrent_unordered_multimapКласс не предоставляет operator[] и at метод.

В следующем примере показана базовая структура для использования 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]
*/

[Top]

Класс concurrent_unordered_set

Класс Concurrency:: concurrent_unordered_set похож на concurrent_unordered_map класс, за исключением того, что он управляет значениями вместо пар "ключ — значение". concurrent_unordered_setКласс не предоставляет operator[] и at метод.

В следующем примере показана базовая структура для использования 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]
*/

[Top]

Класс concurrent_unordered_multiset

Класс Concurrency:: concurrent_unordered_multiset похож на concurrent_unordered_set класс, за исключением того, что он допускает дублирование значений. Он также отличается от concurrent_unordered_set описанных ниже способов.

  • Метод concurrent_unordered_multiset:: INSERT возвращает итератор, а не std::pair<iterator, bool> .

  • concurrent_unordered_multisetКласс не предоставляет operator[] и at метод.

В следующем примере показана базовая структура для использования 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]
*/

[Top]

Класс combinable

Класс Concurrency:: combinable обеспечивает многократно используемое локальное хранилище потока, которое позволяет выполнять детализированные вычисления, а затем объединять эти вычисления в окончательный результат. Объект combinable можно рассматривать как переменную уменьшения.

combinableКласс полезен при наличии ресурса, совместно используемого несколькими потоками или задачами. combinableКласс помогает исключить общее состояние, предоставляя доступ к общим ресурсам без блокировки. Таким образом, этот класс предоставляет альтернативу использованию механизма синхронизации, например мьютекса, для синхронизации доступа к общим данным из нескольких потоков.

Методы и функции

В следующей таблице показаны некоторые важные методы combinable класса. Дополнительные сведения обо всех combinable методах класса см. в разделе класс combinable.

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

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

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

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

Примеры

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

[Top]

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

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

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

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

Ссылка

Класс concurrent_vector

Класс concurrent_queue

Класс concurrent_unordered_map

Класс concurrent_unordered_multimap

Класс concurrent_unordered_set

Класс concurrent_unordered_multiset

Класс combinable