Финал цикла статей о параллелизме
Очень медленный фильм, очень характерный для стиля Андрея Тарковского, Offret/The Sacrifice (1986), имеет следующее вступительное повествование:
«Знаете, иногда я говорю себе, если каждый божий день, в один и тот же удар часов, совершать одно и то же единичное действие, подобное ритуалу, неизменному, систематическому, каждый день в одно и то же время, мир было бы изменено. Да, что-то изменится, это должно быть».
Тарковский верит в добродетель систематизма и настойчивости, но я также должен задаться вопросом, почему это так. И я не могу прийти ни к какому другому ответу, кроме как воскликнуть — «полнота!». Именно ощущение законченной, хорошо сделанной работы приносит удовлетворение душе человека и имеет шанс изменить мир. А добиться этого без систематичности и настойчивости очень сложно.
Если я должен что-то начать, то я обязан это завершить [0]. Если я в какой-то момент перестану читать скучную книгу, перестану смотреть скучный фильм, перестану писать скучное стихотворение или даже кардинально — перестану жить скучной жизнью, то мой мозг будет трепетать от чувства незавершенности, навсегда.
Если я смогу убить все свои желания, свое любопытство, свои интересы и наклонности, тогда я смогу чего-то добиться в долгосрочной перспективе. Могу сказать, что я что-то сделал, закончил работу, написал роман, сочинил оперу, построил дом, посадил дерево. Только так я могу чувствовать себя полноценной и достойной уважения.
Но ладно, не будем драматизировать — все не так серьезно. Святые дымы. Давайте продолжим нашу дискуссию о параллелизме и избавимся от этого бренного мира.
Мы уже рассмотрели ключевые понятия параллелизма в Части I, обсудили некоторые способы предоставления взаимоисключающего доступа к ресурсу в Части II и изучал реализацию параллелизма в языках программирования Ada, Java и C# в Часть III. Опять же, не будем забывать, что все эти статьи в основном основаны на книге Concepts of Programming Languages Robert W. Sebesta.
Сегодня наступает судный день, и Часть IV посвящена реализации параллелизма в функциональных языках программирования. Если у вас возникли трудности с пониманием парадигмы функционального программирования, то вы можете прочитать мой вечно серый эссе о Лиспе.
Параллелизм в Multi-Lisp
Multi-Lisp — это параллельная версия языка программирования Scheme, когда параллелизм вызывается неявно. Для этой цели служит конструкция pcall (макрос). Однако его безопасность находится под вопросом. В результате в центре внимания оказывается конструкция future, которая считается более продуктивной. Как и pcall, он оценивает вызовы функций внутри него в параллельном потоке. Здесь, если родительскому потоку требуется возвращаемое значение от функции, которая не завершила свое выполнение, то родительский поток ожидает ее завершения. Фьючерсы напоминают форкинг в сочетании с ленивой оценкой.
Параллельное машинное обучение
Параллельная версия языка программирования ML. В CML параллелизм поддерживается с помощью потоков и синхронной передачи сообщений. Поток создается с помощью примитива spawn. После создания потока его функциональный параметр начинает выполняться в новом потоке. Это происходит либо с произведенным выводом, либо посредством связи с другими потоками. Завершение потоков, будь то родительский или дочерний, не влияет на выполнение другого.
Для связи потоков друг с другом используются каналы (по аналогии с горутинами и каналами в языке программирования Go). Здесь используются две основные функции — отправить и получить (получить) сообщения. Тип операции отправки ниже предполагается целочисленным, когда recv возвращает значение канала.
let val ch = channel() send(ch, 10) recv(ch)
Отправка и получение могут происходить, когда и отправитель, и получатель готовы (в противном случае они ждут). Функции могут принимать каналы в качестве параметров. Так же, как и в языке программирования Ада, синхронная передача сообщений должна иметь возможность решить, какое сообщение выбрать, когда его получают более чем по одному каналу, когда защищенная команда do-od конструкция делает случайный выбор. Событие — это механизм синхронизации в CML.
F#
F# является частью семейства .NET [1], поэтому его сходство с C# при реализации параллелизма будет выглядеть крайне странно.
let createThread() = let th = new Thread(MethodMan) th.Start()
Синхронизация не требуется для общих неизменяемых данных, но с изменяемыми данными дело обстоит иначе. В этом случае используются замки. Как отмечено в книге, взаимная переменная, выделенная в куче, имеет тип ref. Его можно изменить с помощью лямбда-выражения, в котором используется оператор :=. Префикс ! получает значение ref.
let s = ref 0 lock(s) (fun () -> s := !s + x)
Узнайте об изменяемых и неизменяемых объектах в Python в статье Analytics Vidhya.
Асинхронно вызываемые потоки используют подпрограммы BeginInvoke и EndInvoke и интерфейс IAsyncResult, как и в C# (см. предыдущую статью о параллелизме для получения дополнительной информации).
Параллелизм в Лиспе
Чтобы узнать о параллелизме в Лиспе, обратитесь к статье эта.
Вот и все о функциональных языках программирования. Все кончено, иди домой. Но если вам интересно просмотреть всю главу о параллелизме, продолжайте читать, потому что именно здесь Кэмерон приходит в ярость.
Параллелизм на уровне операторов в высокопроизводительном Fortran
Мы хотим понять, как программист может сообщить компилятору об оптимизации выполнения программ в многопроцессорной архитектуре. Высокопроизводительный Фортран (HPF) обеспечивает это.
Основные операторы спецификации в HPF определяют количество процессоров, распределение данных по их памяти и выравнивание данных с другими данными в памяти. Префикс !HPF$ вводит эти операторы, что выглядит круто. Приведенный ниже оператор сообщает компилятору, что для компиляции кода можно использовать 3 процессора.
!HPF$ PROCCESSORS prcs (3)
Спецификации DISTRIBUTE и ALIGN используются на машинах, где у каждого процессора своя память (см. первую часть цикла статей).
!HPF$ DISTRIBUTE (kind) ONTO prcs :: identifier_list
Тип может быть BLOCK или CYCLIC. Для получения дополнительной информации о том, что они на самом деле делают, обратитесь к разделу 13.10.1 книги. В списке идентификаторов есть имена переменных массива, которые будут распределены.
Align, с другой стороны, определяет, как связать распределение массива с другим массивом.
!HPF$ ALIGN array1_element WITH array_2 element
Другой оператор — FORALL, который указывает последовательность операторов присваивания, которые должны выполняться одновременно.
FORALL (index = 1:100) list_1(index) = list_2(index) END FORALL
FORALL идеально сочетается с векторными машинами. В C# реализованы два похожих метода, которые ведут себя как FORALL: Parallel.For и Parallel.ForEach. [2]
Примечания
[0] Рыбы любят говорить о полёте.
[1] Как и другие языки семейства .NET, F# также может взаимодействовать с другими членами через библиотеку базовых классов (BCL) .NET Framework. Он восходит к языкам семейства ML, так как является строго типизированным языком. Стандартный ML, Caml, а также Haskell повлияли на дизайн F# вместе с ранее упомянутым OCaml, который F# пытается имитировать.
[2] Утверждения, которые мы здесь обсудили, являются только вводными и не показывают полную мощность HPF. Для получения дополнительной информации см. здесь.
Читайте предыдущие части:
Что вы должны знать о параллелизме — Часть I
Ну, мне пришлось как-то заставить себя читать бесконечные главы «Концепции языков программирования Роберта У… >shahaliyev.medium.com»
Что нужно знать о параллелизме — Часть II — Семафоры, мониторы, передача сообщений
«Ты поверишь, Ариадна? Монитор еле защищался.shahaliyev.medium.com»