Поделиться через


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

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

Параллельный контейнер предоставляет безопасный доступ к важнейшим операциям в режиме параллелизма. Функциональные возможности этих контейнеров аналогичны возможностям, предоставляемым библиотекой стандартных шаблонов (STL). Например, класс Concurrency::concurrent_vector напоминает класс std::vector за исключением того, что класс concurrent_vector позволяет параллельно добавлять элементы. Параллельные контейнеры рекомендуется использовать, если имеется параллельный код, требующий прав чтения и записи по отношению к одному и тому же контейнеру.

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

Подразделы

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

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

  • Класс concurrent_vector

  • Класс concurrent_queue

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

  • Класс 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, для удаления всех элементов из объекта concurrent_vector следует использовать метод clear.

  • Класс concurrent_vector не хранит в памяти все элементы подряд. Поэтому класс concurrent_vector невозможно использовать всеми способами, которыми используется массив. Например, для переменной с именем v типа concurrent_vector выражение &v[0]+2 выдает неопределенное поведение.

  • Класс concurrent_vector определяет методы grow_by и grow_to_at_least. Эти методы аналогичны методу resize, но при этом их можно безопасно использовать в режиме параллелизма.

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

  • Среда выполнения не определяет специальную версию concurrent_vector для типа bool.

Безопасные операции в режиме параллелизма

Все методы, добавляющие элементы в объект concurrent_vector, увеличивающие его размер или осуществляющие доступ к элементу в объекте concurrent_vector, являются безопасными в режиме параллелизма. Исключением из этого правила является метод resize.

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

at

end

operator[]

begin

front

push_back

back

grow_by

rbegin

capacity

grow_to_at_least

rend

empty

max_size

size

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

assign

reserve

clear

resize

operator=

shrink_to_fit

Операции, изменяющие значение существующих элементов, не являются безопасными в режиме параллелизма. Для синхронизации параллельных операций чтения и записи в один и тот же элемент данных необходимо использовать объект синхронизации, такой как 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 = v.begin(); i != v.end(); ++i) 
         {
            sums.local() += *i;
         }     
         wcout << L"sum = " << sums.combine(plus<int>()) << endl;
      }
   );
}

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

Безопасность исключений

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

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

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

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

[в начало]

Класс concurrent_queue

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

Различия между классами concurrent_queue и 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 не является безопасным в режиме параллелизма.

Безопасные операции в режиме параллелизма

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

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

empty

push

get_allocator

try_pop

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

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

clear

unsafe_end

unsafe_begin

unsafe_size

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

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

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

Оператор

Описание

operator++

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

operator*

Извлекает ссылку на текущий элемент.

operator->

Извлекает указатель на текущий элемент.

[в начало]

Класс combinable

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

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

Методы и свойства

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

Метод

Описание

local

Извлекает ссылку на локальную переменную, связанную с текущим контекстом потока.

clear

Удаляет все локальные переменные потока из объекта combinable.

combine

combine_each

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

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

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

Примеры

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

[в начало]

Связанные разделы

Ссылки

Класс concurrent_vector

Класс concurrent_queue

Класс combinable