Институт математики, механики и компьютерных наук им. И. И. Воровича
Направление "Фундаментальная информатика и информационные технологии"
2020/2021 уч. г., 4 курс, 1 семестр
Лектор к. ф.-м. н., доцент кафедры алгебры и дискретной математики М. Э. Абрамян
Полный набор видеолекций доступен в виде плейлиста
М. Э. Абрамян.
Параллельное программирование. Курс лекций
Номера, указанные после названия лекции в квадратных скобках, соответствуют пунктам учебника
М. Э. Абрамян. Параллельное программирование на основе технологии MPI 2.0.
Ростов н/Д, Таганрог: Изд-во ЮФУ, 2018, 358 с.
Лекция 1a. Введение в технологию MPI [1.1.1, 1.1.3–1.1.4] – 33:09
00:00 (06:29) Технология MPI: история развития. Изучение технологии MPI с применением электронного задачника PT for MPI-2.
06:29 (09:06) Особенности запуска программ, использующих технологию MPI. Система MPICH, ее версии и особенности установки.
15:36 (04:47) Создание проекта-заготовки с помощью программы Load, входящей в состав задачника PT for MPI-2. Особенности созданного проекта.
20:24 (05:55) Файлы, входящие в созданный проект-заготовку. Файл с функцией Solve, содержащей решение задачи. Файлы системы MPICH. Файлы задачника.
26:19 (06:49) Стартовая функция проекта и ее особенности. Подключение к проекту библиотеки mpich.lib. Запуск программы в параллельном режиме.
Лекция 1b. Введение в технологию MPI [1.1.2–1.1.5] – 52:00
00:00 (05:49) Метод Task для инициализации задания. Демонстрационный режим и настройка языка интерфейса задачника.
05:49 (08:40) Инициализация параллельного режима: функции MPI_Initialized и MPI_Init.
14:29 (05:17) Особенности работы программы, выполняющей задание в параллельном режиме. Управляющая программа mpiexec.exe.
19:47 (04:42) Проблемы, связанные с запуском параллельной программы из среды разработки.
24:29 (03:52) Взаимодействие запущенных экземпляров учебной программы.
28:21 (06:29) Основные понятия MPI-программирования: главный и подчиненные процессы, ранги процессов, коммуникаторы.
34:51 (09:14) Функции MPI_Comm_rank и MPI_Comm_size. Шесть функции MPI, достаточных для реализации параллельной программы. Функция MPI_Finalize.
44:05 (07:54) Выполнение задания MPI1Proc2: ввод исходных данных с применением потока pt. Использование удаленного репозитория задачника. Обработка ошибок ввода.
Лекция 1c. Введение в технологию MPI [1.1.4–1.1.6] – 38:58
00:00 (06:46) Выполнение задания MPI1Proc2 (продолжение): вывод результатов с применением потока pt. Обработка ошибок вывода.
06:46 (05:21) Проверка правильного варианта решения на наборе тестов.
12:07 (06:28) Дополнительные средства проверки решения. Окно справки задачника.
18:36 (07:00) Использование раздела отладки, функции Show и ShowLine.
25:37 (13:21) Обработка ошибок, связанных с зависанием процессов.
Лекция 2a. Двусторонние взаимодействия (MPI) [1.2.1] – 53:06
00:00 (08:37) Блокирующая отправка сообщений: функции MPI_Send, MPI_Bsend, MPI_Ssend, MPI_Rsend, их параметры buf, count, datatype, dest.
08:37 (09:00) Параметры msgtag и comm. Стандартные типы данных MPI.
17:37 (09:36) Особенности стандартного режима отправки сообщений и режима с буферизацией.
27:14 (06:36) Особенности синхронного режима отправки сообщений и режима "по готовности".
33:51 (07:01) Функция блокирующего приема сообщений MPI_Recv, ее параметры.
40:52 (05:14) Особенности параметра status. Маски MPI_ANY_SOURCE и MPI_ANY_TAG.
46:07 (06:59) Функция MPI_Get_count. Константа MPI_STATUS_IGNORE.
Лекция 2b. Двусторонние взаимодействия (MPI) [1.2.1–1.2.2] – 1:03:17
00:00 (03:52) Особенности ситуации, при которой отправитель и получатель сообщения совпадают.
03:52 (04:03) Функция MPI_Probe: предварительный анализ полученного сообщения.
07:56 (09:36) Функции MPI_Buffer_attach и MPI_Buffer_detach: определение и освобождение буфера для режима отправки с буферизацией. Константа MPI_BSEND_OVERHEAD.
17:32 (11:41) Функции MPI_Sendrecv и MPI_Sendrecv_replace: совмещенные запросы на взаимодействие.
29:14 (11:48) Пример: задание MPI2Send11. Первый вариант решения, приводящий к зависанию подчиненных процессов.
41:02 (07:53) Второй вариант решения, приводящий к зависанию главного процесса, и первый способ его исправления.
48:56 (04:22) Проверка решения преподавателем с использованием данных из репозитория и получение студентом результатов этой проверки.
53:19 (09:57) Второй способ исправления решения, удовлетворяющий всем условиям задачи.
Лекция 2c. Двусторонние взаимодействия (MPI) [1.2.3] – 25:08
00:00 (05:02) Функции для неблокирующей пересылки сообщений и их особенности. Параметр request типа MPI_Request (запрос обмена).
05:02 (09:12) Функции групп Wait и Test: проверка завершения неблокирующей операции передачи или приема сообщения.
14:15 (01:52) Функция MPI_Iprobe: неблокирующий анализ полученного сообщения.
16:07 (05:47) Функции группы Init для создания отложенных запросов. Функции группы Start для запуска отложенных запросов. Устойчивость отложенных запросов.
21:55 (03:13) Функции MPI_Wtime и MPI_Wtick, связанные с замером времени.
Лекция 3a. Коллективные операции и операции редукции (MPI) [1.2.4] – 42:37
00:00 (03:42) Особенность функции MPI_Buffer_attach: необходимость динамического выделения памяти для буфера.
03:42 (06:29) Коллективное взаимодействие процессов и его преимущества. Блокирующий характер коллективных операций.
10:11 (05:38) Выделенный процесс root для некоторых коллективных операций и связанные с ним особенности.
15:50 (03:55) Функция MPI_Barrier: синхронизация процессов.
19:45 (04:34) Функция MPI_Bcast: широковещательная рассылка данных из выделенного процесса.
24:20 (09:13) Функция MPI_Gather: сбор данных из всех процессов.
33:33 (09:03) Функция MPI_Gatherv: сбор данных разного размера и их размещение в требуемом порядке в принимающем процессе.
Лекция 3b. Коллективные операции и операции редукции (MPI) [1.2.4–1.2.5] – 40:57
00:00 (04:38) Функции MPI_Scatter и MPI_Scatterv: рассылка исходных данных по частям из выделенного процесса всем процессам коммуникатора. Функции MPI_Allgather и MPI_Allgatherv: сбор данных из всех процессов и их отправка всем процессам коммуникатора.
04:38 (06:15) Функции MPI_Alltoall и MPI_Alltoallv: комбинация операций сбора и рассылки данных по частям. Обзор заданий группы MPI3Coll.
10:54 (08:46) Функция MPI_Alltoallw (MPI-2): расширенный вариант функции MPI_Alltoall.
19:40 (09:17) Операции редукции. Виды групповых операций. Особенности операций MPI_MAXLOC и MPI_MINLOC. Определение новых групповых операций.
28:58 (05:03) Основные функции, реализующие операции редукции: MPI_Reduce, MPI_Allreduce, MPI_Reduce_scatter.
34:01 (06:56) Функция MPI_Scan: реализация частичных операций редукции. Функция MPI_Reduce_scatter_block (MPI-2).
Лекция 3c. Коллективные операции и операции редукции (MPI) [1.2.5] – 31:08
00:00 (08:52) Пример: задание MPI3Call23. Использование вспомогательной структуры данных. Начальный этап решения: ввод исходных данных.
08:52 (04:24) Завершающий этап решения: вызов функции MPI_Allreduce и вывод полученных результатов. Использование стандартного типа MPI_DOUBLE_INT.
13:17 (09:40) Дополнение. Особенности выравнивания полей в структурах данных и учет этих особенностей в библиотеке MPI.
22:58 (08:10) Возможные проблемы, связанные с особенностями выравнивания полей в структурах данных, и их решение.
Лекция 4a. Производные типы и упаковка данных (MPI) [1.2.6] – 48:22
00:00 (08:30) Зачем нужны пользовательские типы данных. Функция MPI_Type_contiguous.
08:30 (06:38) Функция MPI_Type_vector. Пример ее использования для пересылки соседних столбцов матрицы.
15:09 (04:18) Функция MPI_Type_indexed. Пример ее использования для пересылки треугольных матриц.
19:27 (04:54) Функция MPI_Type_create_struct: универсальный способ определения нового типа данных.
24:22 (07:46) Дополнительные функции для определения новых типов данных. Функции MPI_Type_commit и MPI_Type_free.
32:08 (03:08) Протяженность (extent) и размер (size) типов данных MPI; функции для их нахождения.
35:17 (07:03) Задание граничных пустых промежутков: вариант реализации для MPI-1, использующий псевдотипы MPI_LB и MPI_UB.
42:21 (06:00) Задание и изменение граничных промежутков в MPI-2: функция MPI_Type_create_resized. Функция MPI_Type_get_extent (MPI-2).
Лекция 4b. Производные типы и упаковка данных (MPI) [1.2.6] – 39:49
00:00 (06:49) Упаковка данных: функция MPI_Pack. Тип MPI_PACKED.
06:49 (04:21) Распаковка данных: функция MPI_Unpack. Функция MPI_Pack_size и ее особенности.
11:10 (10:03) Обзор заданий группы MPI4Type. Пример: задание MPI4Type14. Знакомство с заданием.
21:13 (03:12) Первый этап решения: ввод исходных данных в главном процессе.
24:26 (03:39) Второй этап решения: определение нового типа данных с помощью средств MPI-1 . Отладочный вывод информации о новом типе.
28:05 (04:11) Завершающий этап решения: регистрация нового типа и отправка данных на стороне главного процесса, прием и вывод данных на стороне подчиненных процессов.
32:17 (07:32) Вариант решения, использующий средства MPI-2 для определения нового типа. Рекомендации по выполнению заданий третьей подгруппы группы MPI4Type.
Лекция 4c. Производные типы и упаковка данных (MPI) [1.2.6] – 22:35
00:00 (07:27) Применение контейнеров vector библиотеки STL в параллельных программах. Итераторы потока ввода и особенности их использования для потока pt задачника.
07:27 (05:42) Необходимость специального задания параметра итератора, обусловленная особенностью синтаксического анализатора языка C++.
13:10 (02:42) Особенности использования данных из контейнера vector в качестве первого параметра функций отправки и приема сообщений.
15:52 (06:42) Использование типа vector для хранения результирующих данных. Использование алгоритма copy совместно с итератором для вывода результатов.
Лекция 5a. Создание новых коммуникаторов (MPI) [1.2.7] – 42:59
00:00 (04:13) Применение новых коммуникаторов для реализации коллективных операций с группами процессов. Интракоммуникаторы и интеркоммуникаторы.
04:13 (14:05) Применение новых коммуникаторов для обеспечения правильной работы параллельных библиотек. Пример, связанный с электронным задачником.
18:19 (04:58) Создание копии существующего коммуникатора: функция MPI_Comm_dup. Сравнение коммуникаторов: функция MPI_Comm_compare.
23:17 (05:37) Создание коммуникаторов на основе групп процессов: функция MPI_Comm_create.
28:55 (10:11) Функции для работы с группами процессов. Сравнение групп. Создание новой группы путем выбора или удаления части процессов из существующей группы.
39:06 (03:52) Теоретико-множественные операции для групп процессов. Освобождение дескрипторов групп и коммуникаторов: функции MPI_Group_free и MPI_Comm_free.
Лекция 5b. Создание новых коммуникаторов (MPI) [1.2.7] – 29:37
00:00 (07:08) Создание коммуникаторов с помощью функции MPI_Comm_split. Пример ее использования: задание MPI5Comm3, знакомство с его формулировкой.
07:08 (08:40) Параметры color и key функции MPI_Comm_split. Особое значение MPI_UNDEFINED для параметра color.
15:48 (04:12) Начальный этап решения: создание нового коммуникатора.
20:01 (09:36) Завершающий этап решения: вызов коллективной функции для созданного коммуникатора и вывод результатов. Использование контейнеров vector для хранения исходных и результирующих данных.
Лекция 6a. Виртуальные топологии (MPI) [1.2.8] – 48:23
00:00 (04:30) Использование виртуальных топологий для дополнительного упорядочивания процессов. Виды виртуальных топологий. Проверка наличия топологии у коммуникатора: функция MPI_Topo_test.
04:30 (06:22) Декартова топология и четыре группы связанных с ней функций MPI.
10:52 (05:19) Пример использования декартовой топологии: задание MPI5Comm17.
16:12 (10:15) Начальный этап решения: создание коммуникатора с декартовой топологией (функция MPI_Cart_create).
26:27 (04:06) Вспомогательный этап решения: определение координат каждого процесса (функция MPI_Cart_coords) и их вывод в разделе отладки.
30:33 (05:08) Функция MPI_Cart_rank для определения ранга процесса по его координатам, ее особенности.
35:42 (09:22) Следующий этап решения: расщепление созданной декартовой решетки (функция MPI_Cart_sub). Параметр remain_dims.
45:04 (03:19) Завершающий этап решения: ввод и пересылка исходных данных, вывод результатов.
Лекция 6b. Виртуальные топологии (MPI) [1.2.8–1.2.9] – 56:45
00:00 (14:34) Пример неверного решения задачи MPI5Comm17 и обсуждение результатов, выведенных в окне задачника.
14:34 (06:10) Функция MPI_Dims_create: нахождение оптимального количества узлов по каждому измерению декартовой решетки.
20:44 (02:48) Функции MPI_Cartdim_get и MPI_Cart_get: восстановление параметров декартовой топологии.
23:32 (04:20) Функция MPI_Cart_shift: определение рангов источника и приемника при пересылке данных вдоль указанной координаты декартовой решетки.
27:53 (04:20) Топология графа и ее особенности. Обзор функций MPI для определения и использования топологии графа.
32:13 (05:11) Пример использования топологии графа: задание MPI5Comm29, знакомство с его формулировкой.
37:24 (09:29) Начальный этап решения: создание коммуникатора с топологией графа (функция MPI_Graph_create). Особенности параметров-массивов index и edges.
46:54 (03:17) Вспомогательный этап решения: вывод параметров-массивов index и edges в разделе отладки. Функции MPI_Graphdims_get и MPI_Graph_get: восстановление параметров топологии графа.
50:12 (06:33) Завершающий этап решения: получение информации о процессах-соседях (функции MPI_Graph_neighbors_count и MPI_Graph_neighbors), ввод исходных данных, их пересылка соседям и вывод полученных результатов.
Лекция 6c. Виртуальные топологии (MPI) [1.3.1] – 21:31
00:00 (04:35) Топология распределенного графа и ее особенности (MPI-2). Дополнительные характеристики распределенного графа: ориентация и вес ребер.
04:35 (05:10) Создание топологии распределенного графа: функции MPI_Dist_graph_create_adjacent и MPI_Dist_graph_create. Параметры функции MPI_Dist_graph_create_adjacent.
09:45 (03:49) Особенности функции MPI_Dist_graph_create.
13:35 (04:06) Пример использования функций MPI_Dist_graph_create и MPI_Dist_graph_create_adjacent для создания требуемой топологии распределенного графа различными способами.
17:41 (03:49) Получение информации о процессах-соседях в распределенном графе (функции MPI_Dist_graph_neighbors_count и MPI_Dist_graph_neighbors).
Лекция 7a. Параллельный ввод-вывод (MPI-2) [1.3.2] – 41:33
00:00 (09:05) Параллельный ввод-вывод (MPI-2) и его возможности. Двоичный формат файлов, используемых при параллельном вводе-выводе.
09:05 (09:34) 24 варианта параллельного файлового доступа, различающиеся координацией (локальная или коллективная), способом позиционирования (явный или с помощью локального или общего указателя) и видом доступа (блокирующий или неблокирующий).
18:39 (10:04) Функции MPI, обеспечивающие различные варианты параллельного файлового доступа.
28:43 (05:46) Параметры функций MPI для блокирующего файлового доступа. Типы MPI_File и MPI_Offset.
34:30 (07:02) Использование параметра типа MPI_Status для функций файлового доступа.
Лекция 7b. Параллельный ввод-вывод (MPI-2) [1.3.2–1.3.3] – 44:20
00:00 (05:44) Функции позиционирования локального или общего файлового указателя: MPI_File_seek и MPI_File_seek_shared. Получение текущей позиции указателя: функции MPI_File_get_position и MPI_File_get_position_shared.
05:44 (08:15) Особенности локальных и коллективных функций, использующих общие файловые указатели.
13:59 (06:07) Базовый тип файла etype и его использование при задании или получении файловой позиции. Получение и изменение размера файла: функции MPI_File_get_size и MPI_File_set_size.
20:06 (05:30) Возможности, связанные с дополнительной настройкой образа файловых данных (file view). Обзор заданий группы MPI6File.
25:36 (10:23) Пример: задание MPI6File26. Особенности заданий, связанных с файловой обработкой.
36:00 (08:20) Знакомство с заданием и обсуждение способа решения, основанного на использовании подходящего файлового образа.
Лекция 7c. Параллельный ввод-вывод (MPI-2) [1.3.3, 2.6] – 45:01
00:00 (03:56) Начальный этап решения: ввод исходных данных и пересылка имени файла из главного процесса во все процессы.
03:56 (10:04) Открытие файла: функция MPI_File_open. Настройка режима доступа к файлу. Закрытие файла: функция MPI_File_close.
14:00 (07:15) Определение файлового образа: функция MPI_File_set_view.
21:15 (07:33) Особенности файлового типа, используемого в файловом образе, и его описание для различных процессов.
28:49 (06:05) Определение файлового типа с помощью функций MPI_Type_vector и MPI_Type_create_resized.
34:55 (06:44) Завершающий этап решения: запись исходных данных в файл с помощью единственного вызова функции MPI_File_write_all.
41:39 (03:21) Обзор преамбулы к группе заданий MPI6File и примечаний к некоторым заданиям, описывающих особенности реализации файлового ввода-вывода в системе MPICH.
Лекция 8a. Односторонние коммуникации (MPI-2) [1.3.4] – 34:24
00:00 (07:02) Односторонние коммуникации (MPI-2) и их аналогия с доступом к общей памяти в многопоточном программировании.
07:02 (09:25) Создание участков памяти ("окон"), доступных другим процессам: функция MPI_Win_create. Параметр disp_unit. Освобождение дескриптора окна: функция MPI_Win_free.
16:28 (07:30) Организация доступа к окну: функции MPI_Get, MPI_Put, MPI_Accumulate. Инициирующий процесс (origin) и целевой процесс (target), их соотнесение с отправителем и получателем для разных вариантов доступа.
23:58 (05:59) Особенности функции MPI_Accumulate.
29:57 (04:26) Проблемы, связанные с синхронизацией доступа к окну.
Лекция 8b. Односторонние коммуникации (MPI-2) [1.3.4–1.3.5] – 48:00
00:00 (06:35) Активный и пассивный целевой процесс. Период доступа (access epoch) для инициирующего процесса, период предоставления доступа (exposure epoch) для активного целевого процесса, их значение для синхронизации результатов односторонних коммуникаций.
06:35 (06:57) Простейший вариант синхронизации: коллективная функция MPI_Win_fence. Параметр assert.
13:32 (09:00) Синхронизация на основе явного указания инициирующих и активных целевых процессов: локальные функции MPI_Win_start, MPI_Win_complete, MPI_Win_post, MPI_Win_wait. Функция MPI_Win_test.
22:32 (05:26) Синхронизация на основе блокировки (случай пассивных целевых процессов): функции MPI_Win_lock и MPI_Win_unlock. Эксклюзивная и совместная блокировка.
27:58 (02:52) Краткий обзор заданий группы MPI7Win. Применение функции MPI_Barrier для дополнительной синхронизации.
30:51 (05:40) Пример с использованием простейшего варианта синхронизации: задание MPI7Win13. Знакомство с заданием.
36:32 (02:33) Начальный этап решения: описание требуемых массивов и ввод исходных данных.
39:06 (02:25) Следующий этап решений: определение окна доступа.
41:32 (06:28) Завершающий этап решения: использование функции MPI_Accumulate для изменения элементов в требуемых окнах и вывод результатов.
Лекция 8c. Односторонние коммуникации (MPI-2) [1.3.6, 2.7] – 34:13
00:00 (11:29) Пример более сложного варианта синхронизации: задание MPI7Win23. Знакомство с заданием.
11:29 (02:22) Начальный этап решения: ввод данных и создание окна доступа.
13:51 (04:19) Следующий этап решения: определение групп процессов и их использование в функциях синхронизации.
18:11 (04:04) Действия на первом этапе односторонних коммуникаций: получение данных с помощью функции MPI_Get и вывод результатов в главном процессе.
22:15 (04:45) Действия на втором этапе односторонних коммуникаций: определение нового периода доступа, накопление данных с помощью функции MPI_Accumulate во всех подчиненных процессах и вывод в них результатов.
27:00 (07:12) Развернутый обзор заданий группы MPI7Win.
Лекция 9a. Введение в технологию OpenMP – 48:11
00:00 (08:19) Технология многопоточного программирования OpenMP. Потоки (нити, threads), их отличия от процессов. Особенности технологии OpenMP.
08:19 (07:09) Стандарты OpenMP. Определение версии OpenMP с помощью макроса _OPENMP. Включение режима поддержки OpenMP в среде Visual Studio. Заголовочный файл omp.h.
15:29 (12:08) Настройка поддержки OpenMP в среде Dev-C++. Автоматическая настройка проектов для задачника PT for OpenMP.
27:38 (09:04) Директивы OpenMP. Непараллельные и параллельные области. Главная и дополнительные нити. Общие (shared) и локальные (private) переменные.
36:43 (04:36) Общий вид директивы OpenMP. Опции директивы, область действия директивы. Задание количества нитей по умолчанию с помощью переменной среды. Функции замера времени: omp_get_wtime и omp_get_wtick.
41:19 (06:51) Директива parallel для создания новой параллельной области и ее опции.
Лекция 9b. Введение в технологию OpenMP – 49:19
00:00 (06:23) Опция reduction. Доступные операции редукции. Схема выполнения операции редукции.
06:23 (06:46) Пример программы с последовательной и параллельной областью. Возможные проблемы при одновременном выводе данных в консольное окно из различных нитей и способы их решения.
13:09 (04:38) Пример, иллюстрирующий применение операции редукции.
17:48 (03:46) Комбинированные директивы OpenMP. Способы задания количества нитей: по умолчанию, функцией omp_set_num_threads, опцией num_threads. Пример применения этих способов.
21:35 (08:52) Функции omp_get_num_procs и omp_get_num_threads, пример применения этих функций. Функция omp_get_max_threads. Настройка вложенных параллельных областей. Функция omp_in_parallel.
30:27 (09:47) Директива single: однократное выполнение фрагмента кода в параллельной области. Опция nowait. Пример использования директивы single. Директива master.
40:15 (09:04) Низкоуровневое управление нитями с использованием функций omp_get_thread_num и omp_get_num_threads. Пример, иллюстрирующий применение этих функций, а также опций private и firstprivate.
Лекция 9c. Введение в технологию OpenMP – 42:41
00:00 (08:53) Распараллеливание цикла: директива for. Опции директивы for.
08:53 (08:05) Опция ordered и директива ordered. Опция nowait. Комбинированная директива parallel for. Ограничения, налагаемые на распараллеливаемый цикл for.
16:59 (06:40) Пример использования директивы for.
23:40 (06:52) Опция schedule: настройка распределения итераций цикла между нитями. Статическое распределение (static) и параметр chunk, пример.
30:33 (07:05) Варианты динамического распределения итераций dynamic и guided, пример.
37:38 (05:03) Другие варианты задания опции schedule: auto и runtime.
Лекция 10a. Синхронизация и балансировка нитей (OpenMP) – 36:03
00:00 (06:00) Неитеративное распараллеливание: директивы sections и section, их опции.
06:00 (05:39) Примеры использования директивы sections.
11:40 (09:26) Особенности использования директивы sections с опцией lastprivate.
21:06 (04:37) Другой вариант неитеративного распараллеливания: задачи (директива task). Опции if и untied директивы task. Директива taskwait. Обзор видов распараллеливания в OpenMP стандарта 2.
25:44 (06:52) Необходимость синхронизации доступа к общей памяти и способы синхронизации, предусмотренные в OpenMP. Первый способ: барьер (директива barrier), пример. Неявно выполняемая директива flush.
32:37 (03:25) Синхронизация (последовательное выполнение) итераций цикла с использованием директивы ordered.
Лекция 10b. Синхронизация и балансировка нитей (OpenMP) – 48:31
00:00 (06:29) Пример использования директивы ordered.
06:29 (06:10) Синхронизация с помощью критических секций: директива critical. Рекомендации по использованию критических секций. Именованные и анонимные критические секции.
12:39 (06:14) Пример программы, использующей критическую секцию.
18:54 (10:01) Эффективная синхронизация оператора, модифицирующего общую переменную: директива atomic, пример. Варианты параллельных алгоритмов, не требующие синхронизации.
28:55 (09:05) Синхронизация с помощью замков (locks). Создание, захват, освобождение и разрушение замка. Множественные замки (nested locks). Пример программы, использующей замок.
38:01 (10:30) Функции, реализующие неблокирующую попытку захвата замка, пример их использования.
Лекция 10c. Синхронизация и балансировка нитей (OpenMP) – 53:48
00:00 (05:56) Распространенные ошибки, возникающие при использовании технологии OpenMP (по материалам статьи "32 подводных камня OpenMP"). Первая группа: логические ошибки. Не включен режим поддержки OpenMP. Отсутствие директивы parallel. Отсутствие текста omp в директивах.
05:56 (08:55) Отсутствие директивы for. Ненужное распараллеливание, зависимость последствий данной ошибки от настройки режима nested.
14:52 (06:45) Неправильное применение директивы ordered. Попытка переопределения количества нитей после входа в параллельную область. Попытка одновременного использования общего ресурса.
21:38 (04:27) Незащищенный доступ к общей памяти. Отсутствие директивы flush. Вторая группа ошибок: ошибки производительности. Ненужная директива flush.
26:05 (06:59) Слишком частое применение критических секций.
33:05 (04:57) Обзор заданий группы OMPBegin, связанных с вычислением двойных суммы. Необходимость балансировки нитей, вызванная различным объемом вычислений для различных слагаемых внешней суммы.
38:02 (05:37) Первая подгруппа заданий: низкоуровневый вариант балансировки нитей (разбиение на полосы или на обратные полосы).
43:40 (04:41) Вторая подгруппа заданий: использование директивы sections и балансировка, основанная на вычислении требуемого числа итераций для каждой нити. Третья подгруппа задач: использование опции schedule директивы for.
48:21 (05:26) Вывод дополнительной информации в разделе отладки. Особенности создаваемых заготовок программ.
Лекция 11a. Параллельные матричные алгоритмы (MPI) – 1:03:11
00:00 (05:38) Параллельное перемножение матриц как пример параллельного матричного алгоритма. Ленточные и блочные алгоритмы.
05:38 (05:55) Этапы параллельных матричных алгоритмов. Первый вариант ленточного алгоритма: использование горизонтальных полос.
11:33 (09:44) Реализация первого варианта ленточного алгоритма.
21:18 (09:37) Второй вариант ленточного алгоритма: использование горизонтальных и вертикальных полос.
30:56 (10:29) Блочные алгоритмы перемножения матриц. Формула для произведения блочных матриц.
41:26 (13:17) Первый вариант блочного алгоритма: алгоритм Фокса. Перераспределение блоков до и после их перемножения на каждой итерации алгоритма.
54:43 (08:27) Второй вариант блочного алгоритма: алгоритм Кэннона. Этап начальной инициализации блоков, упрощающий действия по их последующему перераспределению.
Лекция 11b. Параллельные матричные алгоритмы (MPI) [1.4.1–1.4.5] – 1:12:59
00:00 (08:01) Группа заданий MPI9Matr на разработку алгоритмов перемножения матриц. Средства MPI, используемые в алгоритмах. Особенности заданий на реализацию отдельных этапов алгоритма.
08:01 (04:08) Задания на разработку алгоритма в полном объеме. Оформление каждого этапа алгоритма в виде вспомогательной функции.
12:10 (05:09) Обзор наборов заданий, связанных с каждым из алгоритмов (ленточные алгоритмы 1 и 2, алгоритм Кэннона, алгоритм Фокса).
17:19 (07:52) Примеры. Задача MPI9Matr1: реализация непараллельного алгоритма умножения матриц. Варианты решения с использованием массивов и контейнеров vector.
25:11 (05:51) Задача MPI9Matr2: реализация начального этапа алгоритма (рассылка исходных данных) на примере ленточного алгоритма 1. Знакомство с формулировкой задачи и заготовкой программы.
31:03 (06:57) Вид раздела исходных данных и результатов в окне задачника. Решение задачи MPI9Matr2.
38:01 (07:46) Задача MPI9Matr24: перераспределение блоков на начальном этапе алгоритма Кэннона. Знакомство с формулировкой задачи и заготовкой программы.
45:47 (08:30) Вид раздела исходных данных и результатов в окне задачника. Решение задачи MPI9Matr24.
54:17 (06:23) Задача MPI9Matr19: реализация заключительного этапа алгоритма (сборка результатов) на примере ленточного алгоритма 2 с использованием файлового вывода. Знакомство с формулировкой задачи и заготовкой программы.
1:00:41 (04:12) Вид раздела исходных данных и результатов в окне задачника. Начальный этап решения: ввод исходных данных, пересылка дополнительных данных в подчиненные процессы, создание файла.
1:04:53 (08:05) Завершающий этап решения: определение образа файла и запись данных в файл.
Лекция 12a. Параллельные методы решения задачи n тел (MPI, OpenMP) – 39:37
00:00 (07:05) Постановка задачи о гравитационном взаимодействии n тел. Связанные с ней физические законы и вытекающая из них система дифференциальных уравнений движения; ее сведение к системе разностных уравнений.
07:05 (05:17) Проблема неустойчивого поведения полученного приближенного решения при наличии близко расположенных тел и вариант решения указанной проблемы для модельной системы тел.
12:22 (06:20) Непараллельный вариант алгоритма, решающего задачу n тел. Этапы алгоритма: инициализации системы, вычисление сил взаимодействия, пересчет положения и скорости тел.
18:42 (06:27) Первый (неэффективный) вариант вычисления сил, особенности его программной реализации.
25:10 (07:36) Дополнительные действия: вывод контрольных значений и времени работы алгоритма.
32:47 (06:49) Второй вариант непараллельного алгоритма, его ускорение по сравнению с первым вариантом. Необходимость дополнительной балансировки при распараллеливании данного варианта алгоритма.
Лекция 12b. Параллельные методы решения задачи n тел (MPI, OpenMP) – 33:48
00:00 (03:00) Распараллеливание первого непараллельного варианта алгоритма с применением директивы for.
03:00 (06:58) Попытка распараллеливания второго непараллельного варианта алгоритма с применением директивы for, приводящая к ошибочным результатам. Причина ошибки.
09:58 (05:15) Первый вариант исправления ошибки: применение критической секции. Проблема: существенное увеличение времени работы алгоритма.
15:14 (06:03) Второй вариант исправления ошибки: применение вспомогательных массивов для хранения добавочных сил. Балансировка алгоритма с помощью опции dynamic.
21:17 (07:29) Варианты балансировки, не использующие опцию dynamic: распределение итераций по полосам и по обратным полосам.
28:46 (05:01) Модификация алгоритма распределения итераций по обратным полосам. Итоговое обсуждение результатов, полученных всеми рассмотренными алгоритмами.
Лекция 12c. Параллельные методы решения задачи n тел (MPI, OpenMP) – 1:12:48
00:00 (04:37) Параллельные алгоритмы решения задачи n тел, использующие передачу сообщений (технология MPI). Три модели взаимодействия процессов.
04:37 (04:16) Распределение по процессам различных данных, связанных с задачей n тел.
08:54 (09:52) Модель "управляющий-рабочие" ("портфель задач"), общее описание. Применение этой модели для задачи n тел.
18:47 (04:09) Детали программной реализации алгоритма, основанного на модели "управляющий-рабочий". Алгоритм для управляющего процесса.
22:57 (07:41) Алгоритм для рабочих процессов.
30:38 (02:53) Обзор особенностей алгоритма, основанного на модели "управляющий-рабочие". Пример работы алгоритма.
33:31 (05:33) Модель пульсации и ее применение к задаче n тел. Простейшая реализация этой модели и причины ее несбалансированности. Вариант балансировки, основанный на использовании блоков тел разного размера.
39:05 (03:42) Начальные действия алгоритма по расчету размеров блоков тел для каждого процесса.
42:48 (05:45) Детали программной реализации алгоритма, основанного на модели пульсации. Пример работы алгоритма.
48:33 (06:05) Модель конвейера и ее применение к задаче n тел. Простейшая реализация этой модели, ее неэффективность.
54:39 (04:44) Второй вариант реализации модели конвейера, позволяющий избежать двойного вычисления сил. Несбалансированность данного варианта.
59:23 (03:41) Балансировка алгоритма путем разбиения набора тел на полосы или на обратные полосы.
1:03:05 (04:15) Особенности алгоритма, основанного на модели конвейера. Примеры работы различных вариантов алгоритма.
1:07:20 (05:27) Обзор группы заданий MPIGravit.
Лекция 13a. Интеркоммуникаторы (MPI-2) [1.3.7] – 1:09:06
00:00 (08:12) Интеркоммуникаторы и их отличие от обычных коммуникаторов (интракоммуникаторов). Локальная и удаленная группа процессов. Функции MPI_Comm_remote_size, MPI_Comm_remote_group и MPI_Comm_test_inter.
08:12 (07:01) Проблемы, возникающие при создании интеркоммуникатора, и способ их решения, основанный на процессах-представителях (leaders) из коммуникатора-посредника (peer).
15:14 (04:37) Функция MPI_Intercomm_create для создания интеркоммуникатора, ее параметры local, local_leader, peer_comm, remote-leader.
19:51 (05:31) Параметр tag ("метка безопасности") и выходной параметр intercomm. Другой способ обеспечения безопасности, основанный на использовании копии коммуникатора.
25:23 (04:52) Пример создания и использования интеркоммуникатора: задание MPI8Inter9. Знакомство с заданием.
30:16 (07:31) Этапы выполнения задания. Смысл дополнительного условия (о характеристиках первого процесса каждой половины процессов). Первый этап решения: создание новых интракоммуникаторов и вывод первой части результатов.
37:47 (07:18) Второй этап решения: создание коммуникатора-посредника и объединение ранее созданных интракоммуникаторов в интеркоммуникатор.
45:06 (07:08) Проверка правильности второго этапа решения с помощью вывода отладочной информации. Завершающий этап решения: обмен сообщениями между группами процессов интеркоммуникатора и вывод второй части результатов.
52:14 (05:49) Средства для работы с интеркоммуникаторами в MPI-1 и MPI-2. Создание интеркоммуникаторов с помощью функции MPI_Comm_create (MPI-2).
58:04 (06:21) Создание интеркоммуникаторов с помощью функции MPI_Comm_split (MPI-2). Особенности использования MPI_Comm_split для интеркоммуникаторов в системе MPICH.
1:04:25 (04:40) Коллективные операции для интеркоммуникаторов (MPI-2). Особые значения параметра root для таких операций: MPI_ROOT и MPI_PROC_NULL.
Лекция 13b. Динамическое создание процессов (MPI-2) [1.3.8] – 1:09:04
00:00 (05:23) Динамическое создание процессов (MPI-2); причины добавления этой возможности в стандарт MPI. Функции для создания новых процессов: MPI_Comm_spawn и MPI_Comm_spawn_multiple.
05:23 (05:23) Параметры функции MPI_Comm_spawn. Интеркоммуникатор как результат создания новой группы процессов.
10:47 (06:00) Условия выхода из функции MPI_Comm_spawn. Особенности функции MPI_Comm_spawn_multiple.
16:47 (07:01) Получение из дочерних процессов связанного с ними интеркоммуникатора: функция MPI_Comm_get_parent. Особенности ее применения. Варианты создания нескольких новых наборов процессов.
23:49 (09:24) Задания, посвященные динамическому созданию процессов. Пример: MPI8Inter15. Особенности отладочного вывода для дочерних процессов.
33:13 (04:25) Первый этап решения: создание нового процесса. Проверка правильности создания с помощью отладочного вывода информации о всех процессах приложения.
37:39 (04:55) Второй этап решения: ввод исходных данных в родительских процессах, их пересылка и вывод в дочернем процессе. Использование коллективной операции для интеркоммуникатора.
42:35 (03:41) Завершающий этап решения: пересылка данных из дочернего процесса родительским процессам и их вывод.
46:17 (06:36) Функция MPI_Intercomm_merge: объединение двух групп процессов интеркоммуникатора в новый интракоммуникатор. Параметр high.
52:54 (09:52) Механизм клиент-серверного взаимодействия между не связанными между собой группами процессов. Создание порта для связи и его публичного имени, прослушивание порта на стороне группы-сервера; подключение к порту группы-клиента. Функции MPI, обеспечивающие реализацию этих действий.
1:02:46 (06:17) Описание последовательности действий на стороне сервера и на стороне клиента. Необходимость синхронизации действий сервера и клиента, средства ее обеспечения.
Лекция 14a. Разработка, отладка и запуск параллельных программ (MPI) [1.5.1] – 33:59
00:00 (05:37) Отладка параллельных программ с использованием средств электронного задачника. Группа "заданий" MPIDebug и ее особенности.
05:37 (05:00) Пример: разработка самопланирующего параллельного алгоритма умножения матрицы на вектор. Описание алгоритма и набора тестовых данных.
10:38 (03:16) Создание программы-заготовки для задания MPIDebug10. Вид окна задачника для заданий группы MPIDebug.
13:54 (06:15) Начальный этап решения: генерация исходных данных и рассылка вектора b. Использование функции задачника HideTask.
20:10 (07:21) Основной этап решения. Действия на стороне главного (управляющего) процесса: формирование подзадач и их рассылка, получение и вывод результатов.
27:31 (06:27) Действия на стороне подчиненных (рабочих) процессов: получение подзадач, их обработка и отправка главному процессу. Запуск программы для различного количества процессов. Анализ результатов.
Лекция 14b. Разработка, отладка и запуск параллельных программ (MPI) [1.5.2–1.5.3] – 50:08
00:00 (04:31) Разработка параллельных программ на локальном компьютере без применения электронного задачника. Создание и настройка шаблона консольного приложения для среды Visual Studio 2015. Обзор файлов, входящих в проект.
04:31 (05:20) Разработка простейшей параллельной программы, выводящей ранги всех процессов. Подключение к проекту дополнительных компонентов (заголовочных файлов и откомпилированной библиотеки MPI).
09:51 (04:07) Запуск программы в параллельном режиме с помощью пакетного файла.
13:58 (07:21) Адаптация ранее разработанной параллельной программы умножения матрицы на вектор для консольного приложения. Использование заголовочного файла pt4null.h с пустыми реализациями всех функций задачника.
21:19 (09:07) Использование файлов pt4console.h и pt4console.cpp, переопределяющих функции задачника для вывода отладочных данных в консольное окно. Функция ShowAll.
30:26 (05:49) Копирование содержимого консольного окна в буфер обмена. Перенаправление потока вывода параллельной программы в текстовый файл средствами операционной системы.
36:15 (05:15) Особенности, связанные с настройкой шаблона консольных приложений в среде Visual Studio 2017.
41:31 (08:37) Разработка параллельных программ в среде Dev-C++: создание шаблона консольного приложения, подключение к проекту дополнительных компонентов и запуск программы в параллельном режиме с помощью пакетного файла.
Лекция 14c. Разработка, отладка и запуск параллельных программ (MPI) – 45:33
00:00 (04:33) Разработка и выполнение параллельных программ на удаленном компьютере и необходимые для этого средства.
04:33 (06:37) Терминальная программа PuTTY для удаленного подключения клиентского компьютера. Настройка программы PuTTY.
11:11 (06:45) Установка связи с сервером с помощью программы PuTTY. Пример удаленного подключения к серверу. Работа с файлами и каталогами с применением файлового менеджера Midnight Commander. Редактирование файлов. Перебор предыдущих команд.
17:57 (06:23) Пересылка файлов по протоколу ftp с использованием файлового менеджера FAR. Настройка параметров ftp-подключения. Пример ftp-подключения.
24:20 (06:35) Компиляция программ на Unix-сервере. Компилятор Intel icc и компиляторы gcc и g++, их опции. Получение справки.
30:55 (07:42) Особенности компиляции программ с поддержкой MPI. Системы управления заданиями для запуска параллельной программы на наборе узлов кластера. Пример формирования задания для системы Portable Batch System (PBS).
38:37 (06:55) Запуск задания в системе PBS и получение результатов. Адаптация параллельных программ, разработанных на локальном компьютере с применением отладочных средств электронного задачника, к выполнению на кластере.
Лекция 15a. Введение в технологию LINQ – 33:00
00:00 (04:20) Технология LINQ платформы .NET Framework: общее описание.
04:20 (04:32) Интерфейсы технологии LINQ.
08:53 (04:01) Коллекции, реализующие обобщенный интерфейс IEnumerable. Возможности языка C# 3.0, введенные для поддержки технологии LINQ.
12:54 (06:22) Пример, иллюстрирующий использование LINQ. Запрос фильтрации Where. Делегаты и анонимные делегаты.
19:17 (06:45) Краткая форма записи анонимных делегатов: лямбда-выражения. Понятие последовательности. Описатель var, основанный на выводе типа. Перебор элементов последовательности. Запрос сортировки OrderBy.
26:03 (06:57) Комбинирование запросов и возникающие при этом проблемы. Решение отмеченных проблем: методы расширения. Цепочки запросов. Запросы, возвращающие скалярные значения.
Лекция 15b. Введение в технологию LINQ – 1:03:47
00:00 (05:21) Анонимные типы. Запрос проецирования Select.
05:21 (05:32) Использование описателя var для описания последовательностей с элементами анонимного типа. Строковое представление анонимных типов. Неизменяемость экземпляров анонимных типов. Совместимость анонимных типов по присваиванию.
10:53 (06:52) Особенности хранения последовательностей в памяти. Отложенное (ленивое) выполнение запросов.
17:45 (05:29) Сохранение исходного порядка элементов последовательности при ее преобразованиях. Пример, иллюстрирующий особенности отложенной обработки последовательностей. Использование внешних переменных в лямбда-выражениях. Запрос экспортирования ToArray.
23:15 (03:31) Обзор запросов LINQ. Особенности описания параметров, являющихся лямбда-выражениями.
26:47 (03:04) Запросы фильтрации Where, TakeWhile, SkipWhile, Take, Skip.
29:51 (03:08) Запросы Distinct, Reverse, DefaultIfEmpty.
33:00 (03:21) Запросы сортировки OrderBy, OrderByDescending, ThenBy, ThenByDescending. Обобщенный интерфейс IOrderedEnumerable.
36:22 (05:28) Сортировка по набору ключей и ее устойчивость.
41:51 (01:57) Сцепление Concat и теоретико-множественные операции для последовательностей.
43:48 (04:57) Запросы проецирования Select и SelectMany, примеры их применения.
48:46 (03:02) Использование запросов Select и SelectMany для построения декартова произведения двух последовательностей (набора упорядоченных пар элементов).
51:48 (06:18) Запросы объединения Join и GroupJoin, особенности их реализации.
58:07 (05:40) Примеры использования запросов объединения. Особенность построения плоского левого внешнего объединения (необходимость использования запроса DefaultIfEmpty).
Лекция 15c. Введение в технологию LINQ – 45:01
00:00 (04:25) Запрос группировки GroupBy, его перегруженные варианты. Простейшие варианты запроса, использующие обобщенный интерфейс IGrouping.
04:25 (05:13) Варианты запроса GroupBy с явной настройкой результирующей последовательности. Примеры применения различных вариантов запроса GroupBy.
09:38 (09:07) Запросы импортирования OfType и Cast. Фильтрация элементов по типу с помощью запроса OfType, ее ограничения.
18:45 (06:40) Запросы экспортирования ToArray, ToList, ToDictionary, ToLookup, AsEnumerable, AsQueryable. Поэлементные операции First, Last, Single и их модификации. Неэффективность доступа к элементу последовательности с помощью запроса ElementAt.
25:26 (07:31) Запросы агрегирования Count, Average, Sum, Max, Min. Использование запросов Average и Sum для nullable-типов. Универсальный агрегирующий запрос Aggregate и его варианты.
32:57 (08:04) Примеры использования запроса Aggregate, в том числе реализация запроса, возвращающего строковое представление произвольной последовательности.
41:02 (03:58) Запросы-квантификаторы All и Any. Запросы Contains и SequenceEqual. Запросы генерирования последовательностей Empty, Repeat, Range и особенности их применения.
Лекция 16a. Выражения запросов LINQ – 39:47
00:00 (05:58) Выражения запросов (query expressions) и причина их включения в технологию LINQ.
05:58 (04:21) Выражения запросов в других языках платформы .NET.
10:20 (06:36) Грамматика выражений запросов. Начальное выражение from и выражение объединения join.
16:57 (05:30) Выражение отбора where. Выражение let для введения новых идентификаторов. Повторение выражения from. Выражение сортировки orderby.
22:27 (04:35) Завершающие выражения select и group. Выражение into для перенаправления полученной последовательности следующему выражению запроса.
27:02 (07:30) Перевод различных конструкций выражений запросов на язык обычных запросов LINQ. Построение декартова произведения набора последовательностей с использованием выражений запросов.
34:32 (05:14) Перевод выражения группировки group в запрос GroupBy. Перевод выражения сортировки orderby в цепочку запросов сортировки. Выражение объединения join и запросы Join и GroupJoin. Построение цепочки выражений запросов с помощью выражения into.
Лекция 16b. Эффективность параллельных программ – 31:28
00:00 (03:45) Эффективность параллельных программ.
03:45 (05:03) Закон Амдала.
08:48 (04:31) Следствие из закона Амдала: формула для верхней границы возможного ускорения параллельной программы. График зависимости теоретически достижимого ускорения от числа процессоров.
13:20 (04:40) Технические издержки, характерные для параллельных программ. Необходимость балансировки параллельного алгоритма. Динамическое распределение нагрузки. Портфель задач (модель "управляющий-рабочие").
18:00 (07:56) Необходимость синхронизации доступа в системах с общей памятью. Способы уменьшения накладных расходов, связанных с синхронизацией. Кэш-память и связанные с ней проблемы для многопроцессорных систем. Механизм сохранения когерентности кэш-памяти.
25:57 (05:30) Параллельные программы для систем с распределенной памятью и свойства коммуникационный среды. Характеристики коммуникационной среды: полоса пропускания и латентность, примеры. Способы повышения эффективности распределенных параллельных программ.
Лекция 16c. Технология Parallel LINQ (PLINQ) и класс Parallel – 1:24:18
00:00 (05:13) Технология Parallel LINQ (PLINQ) - высокоуровневая параллельная технология для платформы .NET. Запрос AsParallel: распределение действий по последующей обработке данных из исходной последовательности по различным нитям.
05:13 (06:28) Пример: применение технологии PLINQ для нахождения всех простых чисел из указанного диапазона.
11:41 (03:26) Классы PLINQ: обобщенный класс ParallelQuery (параллельная последовательность) и реализация для него всех запросов LINQ как методов расширения обобщенного класса ParallelEnumerable. Запросы AsParallel и AsSequential.
15:07 (03:37) Запросы класса ParallelEnumerable для создания параллельных последовательностей: Range и Repeat. Запросы с двумя последовательностями. Неэффективность многократного вызова запроса AsParallel. Возможный отказ системы PLINQ от распараллеливания.
18:45 (06:24) Сохранение порядка следования элементов в параллельных последовательностях: запрос AsOrdered. Ограничения на распараллеливание некоторых запросов.
25:09 (07:57) Настройка обязательного выполнения запросов в параллельном режиме: запрос WithExecutionMode. Указание рекомендуемого числа потоков: запрос WithDegreeOfParallelism. Побочные эффекты в PLINQ.
33:07 (02:45) Завершающая обработка параллельных фрагментов последовательности без их слияния: запрос ForAll.
35:53 (04:09) Варианты распределения элементов последовательности по потокам: блочное (динамическое), диапазонное (статическое) и hash-распределение.
40:02 (06:20) Особенности блочного и диапазонного распределения и способы их настройки.
46:23 (05:33) Непараллельные варианты запроса Aggregate и возможности по их распараллеливанию.
51:56 (08:38) Параллельный вариант запроса Aggregate с параметром - фабрикой аккумуляторов. Пример: подсчет частоты появления букв в строке. Запрос Zip.
1:00:35 (06:02) Высокоуровневое распараллеливание задач на основе класса Parallel платформы .NET. Метод Invoke класса Parallel, пример его использования.
1:06:37 (02:42) Использование безопасных к потокам (thread-safe) коллекций для объединения результатов, полученных из различных задач.
1:09:20 (05:52) Распараллеливание циклов с помощью методов For и ForEach класса Parallel, примеры. Вариант метода ForEach, позволяющий отслеживать номер итерации.
1:15:12 (03:50) Средства досрочного прекращения циклов в методах For и ForEach, примеры их использования. Возвращаемые значения методов For и ForEach.
1:19:03 (05:14) Типы коллекций, безопасных к потокам: ConcurrentStack, ConcurrentQueue, ConcurrentBag, ConcurrentDictionary. Причина отсутствия параллельного аналога списка List. Интерфейс IProducerConsumerCollection, его методы TryAdd и TryTake, их реализация для различных типов коллекций.