В первые дни FORTRAN и BASIC, по существу, все программы были написаны с заявлениями GOTO. Результатом стал код спагетти, а решение - структурированное программирование.
Точно так же указателям может быть сложно контролировать характеристики в наших программах. C ++ начинался с множества указателей, но использование ссылок рекомендуется. Такие библиотеки, как STL, могут уменьшить нашу зависимость. Существуют также идиомы для создания интеллектуальных указателей с лучшими характеристиками, а некоторые версии C ++ допускают ссылки и управляемый код.
Практики программирования, такие как наследование и полиморфизм, используют множество указателей за кулисами (так же, как и в случае, когда структурированное программирование генерирует код, заполненный инструкциями ветвления). Такие языки, как Java, устраняют указатели и используют сборщик мусора для управления динамически размещаемыми данными вместо того, чтобы полагаться на то, что программисты соответствуют всем их новым и удаляемым операторам.
В моем чтении я видел примеры многопроцессорного и многопоточного программирования, которые, кажется, не используют семафоры. Используют ли они одно и то же с разными именами или у них есть новые способы структурирования защиты ресурсов от одновременного использования?
Например, конкретным примером системы многопоточного программирования с многоядерными процессорами является OpenMP. Он представляет собой критическую область следующим образом без использования семафоров, которые, по-видимому, не включены в среду.
th_id = omp_get_thread_num();
#pragma omp critical
{
cout << "Hello World from thread " << th_id << '\n';
}
Этот пример является выдержкой из: http://en.wikipedia.org/wiki/OpenMP
В качестве альтернативы аналогичная защита потоков друг от друга с использованием семафоров с функциями wait () и signal () может выглядеть следующим образом:
wait(sem);
th_id = get_thread_num();
cout << "Hello World from thread " << th_id << '\n';
signal(sem);
В этом примере все довольно просто, и достаточно простого обзора, чтобы показать, что вызовы wait () и signal () совпадают, и даже при большом параллелизме обеспечивается безопасность потоков. Но другие алгоритмы более сложны и используют несколько семафоров (как двоичных, так и счетных), распределенных по нескольким функциям со сложными условиями, которые могут вызываться многими потоками. Последствиями создания тупиковой ситуации или неспособности обеспечить безопасность потоков может быть трудно справиться.
Устраняют ли такие системы, как OpenMP, проблемы с семафорами?
Перемещают ли они проблему куда-нибудь еще?
Как мне преобразовать мой любимый семафор, используя алгоритм, чтобы больше не использовать семафоры?
источник
Ответы:
Существуют ли параллельные методы и практики программирования, которые больше не следует использовать? Я бы сказал, да .
Одним из ранних методов параллельного программирования, который сегодня кажется редким, является программирование на основе прерываний . Вот как UNIX работал в 1970-х годах. См. Комментарий Льва о UNIX или о дизайне Баха операционной системы UNIX . Вкратце, техника заключается в том, чтобы временно приостанавливать прерывания при манипулировании структурой данных, а затем восстанавливать прерывания после этого. Страница справочника BSD spl (9)есть пример этого стиля кодирования. Обратите внимание, что прерывания ориентированы на аппаратное обеспечение, а код воплощает неявную связь между типом аппаратного прерывания и структурами данных, связанными с этим оборудованием. Например, код, который управляет буферами дискового ввода-вывода, должен приостанавливать прерывания от оборудования контроллера диска при работе с этими буферами.
Этот стиль программирования использовался операционными системами на однопроцессорном оборудовании. Для приложений было гораздо реже иметь дело с прерываниями. В некоторых ОС были программные прерывания, и я думаю, что люди пытались построить поверх них системы потоков или сопрограмм, но это было не очень широко распространено. (Конечно, не в мире UNIX.) Я подозреваю, что программирование в стиле прерываний сегодня ограничивается небольшими встроенными системами или системами реального времени.
Семафоры - это преимущество перед прерываниями, потому что они представляют собой программные конструкции (не связанные с аппаратным обеспечением), они предоставляют абстракции по сравнению с аппаратными средствами и обеспечивают многопоточность и многопроцессорность. Основная проблема в том, что они неструктурированы. Программист отвечает за поддержание взаимосвязи между каждым семафором и структурами данных, которые он защищает, по всему миру во всей программе. По этой причине я думаю, что голые семафоры сегодня используются редко.
Другим небольшим шагом вперед является монитор , который заключает в себе механизмы управления параллелизмом (блокировки и условия) с защищаемыми данными. Это было перенесено в систему Mesa (альтернативное соединение) и оттуда в Java. (Если вы прочитаете этот документ Mesa, вы увидите, что блокировки и условия Java-монитора почти дословно копируются из Mesa.) Мониторы полезны тем, что достаточно осторожный и старательный программист может безопасно писать параллельные программы, используя только локальные рассуждения о коде и данных. в мониторе.
Существуют дополнительные библиотечные конструкции, такие как в
java.util.concurrent
пакете Java , которые включают в себя множество высококонкурентных структур данных и конструкций пула потоков. Они могут быть объединены с дополнительными методами, такими как удержание нити и эффективная неизменность. См. Java Concurrency In Practice by Goetz et. и др. для дальнейшего обсуждения. К сожалению, многие программисты все еще используют свои собственные структуры данных с блокировками и условиями, когда им действительно нужно просто использовать что-то вроде ConcurrentHashMap, где тяжелая работа уже была проделана авторами библиотеки.Все вышеперечисленное имеет некоторые важные характеристики: они имеют несколько потоков управления, которые взаимодействуют в глобально разделяемом и изменчивом состоянии . Проблема в том, что программирование в этом стиле все еще очень подвержено ошибкам. Небольшая ошибка может остаться незамеченной, что приведет к неправильному поведению, которое трудно воспроизвести и диагностировать. Может случиться так, что ни один программист не будет «достаточно осторожным и усердным» для разработки больших систем таким способом. По крайней мере, очень немногие. Итак, я бы сказал, что многопоточного программирования с общим изменяемым состоянием следует избегать, если это вообще возможно.
К сожалению, не совсем ясно, можно ли этого избежать во всех случаях. Много программирования все еще делается таким образом. Было бы неплохо увидеть это вытесненным чем-то другим. Ответы Джаррода Роберсона и davidk01 указывают на такие методы, как неизменяемые данные, функциональное программирование, STM и передача сообщений. Их можно много рекомендовать, и все они активно развиваются. Но я не думаю, что они полностью заменили старое доброе старое изменяемое состояние.
РЕДАКТИРОВАТЬ: вот мой ответ на конкретные вопросы в конце.
Я не знаю много об OpenMP. У меня сложилось впечатление, что это может быть очень эффективно для очень параллельных задач, таких как численное моделирование. Но это не кажется универсальным. Семафорные конструкции кажутся довольно низкоуровневыми и требуют, чтобы программист поддерживал связь между семафорами и общими структурами данных со всеми проблемами, которые я описал выше.
Если у вас есть параллельный алгоритм, который использует семафоры, я не знаю каких-либо общих методов для его преобразования. Вы можете преобразовать его в объекты, а затем построить вокруг него некоторые абстракции. Но если вы хотите использовать что-то вроде передачи сообщений, я думаю, вам действительно нужно переосмыслить всю проблему.
источник
Ответ на вопрос
Общим согласием является то, что общее изменяемое состояние - это Bad ™, а неизменное состояние - это Good ™, что снова и снова подтверждается и подтверждается функциональными и императивными языками.
Проблема заключается в том, что основные императивные языки просто не предназначены для такой работы, для этих языков ситуация не изменится за ночь. Здесь сравнение с
GOTO
ошибкой. Неизменное состояние и передача сообщений - отличное решение, но это не панацея.Ущербное помещение
Этот вопрос основан на сравнении с ошибочной предпосылкой; это
GOTO
была актуальная проблема, которая была признана Всемирным советом дизайнеров языков и союзов разработчиков программного обеспечения универсально ©! БезGOTO
механизма ASM не будет работать вообще. То же самое можно сказать и о том, что необработанные указатели являются проблемой в C или C ++, а некоторые считают, что умные указатели являются панацеей, но это не так.GOTO
не проблема, программисты были проблемой. То же самое касается общего изменяемого состояния . Это само по себе не является проблемой , это проблема программистов, использующих его. Если бы был способ генерировать код, который использовал бы изменяемое общее состояние таким образом, чтобы никогда не было никаких условий гонки или ошибок, то это не было бы проблемой. Как и в случае, если вы никогда не пишете спагетти-код сGOTO
эквивалентными конструкциями, это тоже не проблема.Образование - это панацея
Идиотские программисты - это то
deprecated
, что было , у каждого популярного языка все еще естьGOTO
конструкция, прямо или косвенно, и этоbest practice
когда правильно используется в каждом языке, который имеет этот тип конструкций.ПРИМЕР: в Java есть метки, и
try/catch/finally
обе они напрямую работают какGOTO
операторы.Большинство Java-программистов, с которыми я общаюсь, даже не знают, что на
immutable
самом деле означает вне их, повторяяthe String class is immutable
с зомби-взглядом в их глазах. Они определенно не знают, как правильно использоватьfinal
ключевое слово для созданияimmutable
класса. Поэтому я почти уверен, что они понятия не имеют, почему передача сообщений с использованием неизменяемых сообщений так хороша, и почему общее изменяемое состояние не так велико.источник
Последняя ярость в академических кругах, по-видимому, - программная транзакционная память (STM), и она обещает вынести все волосатые детали многопоточного программирования из рук программистов, используя достаточно умную технологию компиляции. За кулисами это все еще замки и семафоры, но вам, как программисту, не нужно беспокоиться об этом. Преимущества такого подхода до сих пор не ясны, и нет очевидных претендентов.
Эрланг использует передачу сообщений и агентов для параллелизма, и это более простая модель для работы, чем STM. С передачей сообщений вам абсолютно не нужно беспокоиться о блокировках и семафорах, потому что каждый агент работает в своей собственной мини-вселенной, поэтому нет связанных с данными условий гонки. У вас все еще есть некоторые странные крайние случаи, но они далеко не такие сложные, как живые и тупиковые блокировки. Языки JVM могут использовать Akka и получать все преимущества передачи сообщений и актеров, но в отличие от Erlang JVM не имеет встроенной поддержки акторов, поэтому в конце дня Akka по-прежнему использует потоки и блокировки, но вы как программисту не нужно беспокоиться об этом.
Другая известная мне модель, которая не использует блокировки и потоки, это использование фьючерсов, что на самом деле является еще одной формой асинхронного программирования.
Я не уверен, насколько эта технология доступна в C ++, но есть вероятность, что если вы видите что-то, что не использует явно потоки и блокировки, то это будет один из вышеперечисленных методов управления параллелизмом.
источник
Я думаю, что это в основном об уровнях абстракции. Довольно часто в программировании полезно абстрагировать некоторые детали таким образом, чтобы он был более безопасным или более читабельным или что-то в этом роде.
Это относится и к структурам управления:
if
s,for
S и дажеtry
-catch
блоки просто абстракции надgoto
с. Эти абстракции почти всегда полезны, потому что они делают ваш код более читабельным. Но есть случаи, когда вам все равно придется использоватьgoto
(например, если вы пишете сборку вручную).Это также относится к управлению памятью: интеллектуальные указатели C ++ и GC являются абстракциями над необработанными указателями и ручным выделением / выделением памяти. И иногда эти абстракции не подходят, например, когда вам действительно нужна максимальная производительность.
То же самое относится и к многопоточности: такие вещи, как фьючерсы и акторы, являются просто абстракциями над потоками, семафорами, мьютексами и инструкциями CAS. Такие абстракции могут помочь вам сделать ваш код более читабельным, а также избежать ошибок. Но иногда они просто не подходят.
Вы должны знать, какие инструменты у вас есть, и в чем их преимущества и недостатки. Затем вы можете выбрать правильную абстракцию для вашей задачи (если есть). Более высокие уровни абстракции не осуждают более низкие уровни, всегда будут случаи, когда абстракция не подходит, и лучший выбор - использовать «старый способ».
источник
Да, но вы вряд ли столкнетесь с некоторыми из них.
В прежние времена было распространено использование методов блокировки (синхронизация барьеров), потому что писать хорошие мьютексы было сложно. Вы все еще можете увидеть следы этого в недавних вещах. Использование современных библиотек параллелизма дает вам гораздо более богатый и тщательно протестированный набор инструментов для распараллеливания и координации между процессами.
Аналогично, старая практика заключалась в том, чтобы писать мучительный код, чтобы вы могли понять, как распараллелить его вручную. Эта форма (потенциально вредная, если вы ошибаетесь) оптимизации также в значительной степени исчезла с появлением компиляторов, которые делают это для вас, разматывая циклы при необходимости, предсказательно следуя ветвям и т. Д. Однако это не новая технология будучи на рынке не менее 15 лет. Использование таких вещей, как пулы потоков, также позволяет обойти некоторый хитрый код прошлого года.
Так что, возможно, устарелая практика - писать код параллелизма самостоятельно, а не использовать современные, хорошо протестированные библиотеки.
источник
Grand Central Dispatch от Apple - это элегантная абстракция, которая изменила мои представления о параллелизме. Сосредоточение на очередях делает реализацию асинхронной логики на порядок проще, по моему скромному опыту.
Когда я программировал в средах, где это доступно, он заменил большую часть моего использования потоков, блокировок и связи между потоками.
источник
Одним из основных изменений в параллельном программировании является то, что процессоры работают значительно быстрее, чем раньше, но для достижения этой производительности требуется хорошо заполненный кэш. Если вы попытаетесь запустить несколько потоков одновременно, меняя их между собой непрерывно, вы почти всегда будете аннулировать кэш для каждого потока (т. Е. Каждому потоку требуются разные данные для работы), и в итоге производительность будет снижена гораздо больше, чем вы. используется для более медленных процессоров.
Это одна из причин, почему асинхронные или основанные на задачах (например, Grand Central Dispatch или Intel TBB) платформы более популярны, они выполняют задачу кода 1 за один раз, выполняя ее до перехода к следующей - однако вы должны кодировать каждую каждая задача занимает немного времени, если вы не хотите испортить дизайн (т.е. ваши параллельные задачи действительно поставлены в очередь). Задачи с интенсивным использованием ЦП передаются на альтернативное ядро ЦП, а не обрабатываются в одном потоке, обрабатывающем все задачи. Его также легче управлять, если не происходит действительно многопоточная обработка.
источник