Для решения этой конкретной проблемы библиотека Intel® Threading Building Blocks включает специальные конструкции. Intel® TBB – это кроссплатформенная библиотека, помогающая в многопоточном программировании. Мы могли бы рассматривать сущности, участвующие в вашем приложении, как четырех разных поставщиков задач. Один тип задач — это задачи ввода — те, которые предоставляют входные данные, другой тип задач обеспечивается первой подпрограммой манипуляции и так далее.
Таким образом, единственное, что нужно сделать пользователю, это предоставить тело для этих задач. В библиотеке есть несколько API для указания, какие тела обрабатывать и как это делать параллельно. Все остальное (здесь я имею в виду создание потоков, синхронизацию между выполнением задач, балансировку работы и т. д.) делает библиотека.
Самый простой вариант решения, который пришел мне в голову, это использование parallel_pipeline. Вот прототип:
#include "tbb/pipeline.h"
using namespace tbb;
int main() {
parallel_pipeline(/*specify max number of bodies executed in parallel, e.g.*/16,
make_filter<void, input_data_type>(
filter::serial_in_order, // read data sequentially
[](flow_control& fc) -> input_data_type {
if ( /*check some stop condition: EOF, etc.*/ ) {
fc.stop();
return input_data_type(); // return dummy value
}
auto input_data = read_data();
return input_data;
}
) &
make_filter<input_data_type, manipulator1_output_type>(
filter::parallel, // process data in parallel by the first manipulator
[](input_data_type elem) -> manipulator1_output_type {
auto processed_elem = manipulator1::process(elem);
return processed_elem;
}
) &
make_filter<manipulator1_output_type, manipulator2_output_type>(
filter::parallel, // process data in parallel by the second manipulator
[](manipulator1_output_type elem) -> manipulator2_output_type {
auto processed_elem = manipulator2::process(elem);
return processed_elem;
}
) &
make_filter<manipulator2_output_type, void>(
filter::serial_in_order, // visualize frame by frame
[](manipulator2_output_type elem) {
visualize(elem);
}
)
);
return 0;
}
при условии реализации необходимых функций (read_data, visualize). Здесь input_data_type
, manipulator1_output_type
и т. д. — это типы, которые передаются между этапами конвейера, а функции process
манипулятора выполняют необходимые вычисления с переданными аргументами.
Кстати, чтобы избежать работы с блокировками и другими примитивами синхронизации, вы можете использовать concurrent_bounded_queue из библиотеки и поместить свои входные данные в эту очередь, возможно, другим потоком (например, выделенным для операций ввода-вывода), таким простым, как concurrent_bounded_queue_instance.push(elem)
, а затем прочитать его через input_data_type elem; concurrent_bounded_queue_instance.pop(elem)
. Обратите внимание, что извлечение элемента здесь является блокирующей операцией. concurrent_queue
обеспечивает неблокирующую альтернативу try_pop
.
Другой вариант — использовать tbb::flow_graph
и его узлы для организации той же схемы конвейерной обработки. Взгляните на два примера, которые описывают зависимость и данные потоковые графы. Возможно, вам потребуется использовать sequencer_node для правильного порядка выполнения элементов (при необходимости).
Стоит прочитать ТАК вопросы, помеченные тегом tbb чтобы увидеть, как другие люди используют эту библиотеку.
person
Aleksei Fedotov
schedule
05.06.2017