Лучший программный подход / методология для обеспечения безопасности потоков - PullRequest
8 голосов
/ 02 ноября 2008

Когда я изучал Java, основываясь на 20-летнем опыте процедурного программирования на базовых языках, Pascal, COBOL и C, я думал, что в то время самым сложным в этом было сосредоточиться на жаргоне и концепциях ООП. Теперь, когда у меня за плечами было около 8 лет прочной Java, я пришел к выводу, что самая сложная вещь в программировании на Java и подобных языках, таких как C #, - это многопоточные / параллельные аспекты.

Кодирование надежных и масштабируемых многопоточных приложений просто сложно! И с тенденцией к тому, что процессоры растут «шире», а не быстрее, они быстро становятся просто критически важными.

Самая сложная область - это, конечно, управление взаимодействиями между потоками и возникающими ошибками: взаимоблокировки, условия гонки, устаревшие данные и задержка.

Итак, мой вопрос к вам заключается в следующем: какой подход или методологию вы используете для создания безопасного параллельного кода, одновременно снижая вероятность возникновения тупиковых ситуаций, задержки и других проблем? Я пришел к подходу, который немного необычен, но очень хорошо работал в нескольких крупных приложениях, и я поделюсь подробным ответом на этот вопрос.

Ответы [ 15 ]

7 голосов
/ 02 ноября 2008

Это относится не только к Java, но и к многопоточному программированию в целом. Я избегаю большинства проблем с параллелизмом и задержкой, просто следуя этим рекомендациям:

1 / Пусть каждый поток запустит свое собственное время жизни (то есть решит, когда умереть). Это может быть запрошено извне (скажем, переменная флага), но оно полностью ответственно.

2 / Имеют все потоки выделяют и освобождают свои ресурсы в одном порядке - это гарантирует, что тупик не произойдет.

3 / Блокировка ресурсов на максимально короткое время.

4 / Передайте ответственность за данные вместе с самими данными - как только вы уведомите поток о том, что данные должны обрабатываться, оставьте его в покое , пока ответственность не будет возвращена вам.

6 голосов
/ 02 ноября 2008

Существует целый ряд техник, которые сейчас входят в общественное сознание (как в последние несколько лет). Большой был бы актерами. Это то, что Эрланг впервые применил к сетке, но оно было перенесено на новые языки, такие как Scala (актеры в JVM). Хотя актеры не решают всех проблем, они действительно намного упрощают анализ вашего кода и выявление проблемных мест. Они также значительно упрощают разработку параллельных алгоритмов из-за того, что они заставляют вас использовать продолжение, передаваемое через общее изменяемое состояние.

Fork / Join - это то, на что вам следует обратить внимание, особенно если вы работаете в JVM. Дуг Ли написал основную статью на эту тему, но многие исследователи обсуждали ее на протяжении многих лет. Насколько я понимаю, эталонную среду Дуга Ли планируется включить в Java 7.

На немного менее инвазивном уровне, часто единственные шаги, необходимые для упрощения многопоточного приложения, это просто уменьшить сложность блокировки. Мелкозернистая блокировка (в стиле Java 5) хороша для пропускной способности, но очень и очень трудно понять, как правильно. Одним из альтернативных подходов к блокировке, набирающих популярность благодаря Clojure, является программно-транзакционная память (STM). По сути, это противоположно обычной блокировке в том, что она скорее оптимистичная, чем пессимистичная. Вы начинаете с предположения, что у вас не будет никаких коллизий, а затем позволяете инфраструктуре устранять проблемы, если и когда они возникают. Базы данных часто работают таким образом. Это отлично подходит для пропускной способности в системах с низкой частотой столкновений, но большой выигрыш заключается в логической компонентизации ваших алгоритмов. Вместо того, чтобы произвольно связывать блокировку (или серию блокировок) с некоторыми данными, вы просто заключаете опасный код в транзакцию и позволяете инфраструктуре выяснить остальное. Вы даже можете получить достаточное количество проверок во время компиляции из приличных реализаций STM, таких как монада GHC STM или мой экспериментальный Scala STM.

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

5 голосов
/ 02 ноября 2008

Нет одного верного ответа для обеспечения безопасности потоков в Java. Однако есть по крайней мере одна действительно замечательная книга: Параллелизм Java на практике . Я регулярно упоминаю об этом (особенно онлайн-версию Safari, когда я нахожусь в путешествии).

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

5 голосов
/ 02 ноября 2008
  1. Избегайте обмена данными между потоками, где это возможно (скопируйте все).
  2. Никогда не блокируйте вызовы методов для внешних объектов, где это возможно.
  3. Храните замки в течение кратчайшего возможного времени.
4 голосов
/ 02 ноября 2008

Обычно я придерживаюсь подхода стиля Эрланга. Я использую Active Object Pattern. Это работает следующим образом.

Разделите ваше приложение на очень крупнозернистые единицы. В одном из моих текущих приложений (400.000 LOC) у меня есть ок. 8 из этих крупнозернистых единиц. Эти устройства не имеют общих данных. Каждый блок хранит свои собственные локальные данные. Каждый модуль работает в своем собственном потоке (= шаблон активного объекта) и, следовательно, является однопоточным. Вам не нужны замки внутри юнитов. Когда юнитам нужно отправлять сообщения другим юнитам, они делают это, отправляя сообщение в очередь других юнитов. Другой блок выбирает сообщение из очереди и реагирует на это сообщение. Это может вызвать другие сообщения для других подразделений. Следовательно, единственные блокировки в этом типе приложения находятся вокруг очередей (одна очередь и блокировка на единицу). Эта архитектура по определению свободна от тупиков!

Эта архитектура очень хорошо масштабируется, ее очень легко внедрить и расширить, как только вы поймете основной принцип. Ему нравится думать об этом как о SOA в приложении.

Помните, разделив ваше приложение на блоки. Оптимальное количество длинных потоков на ядро ​​процессора составляет 1.

3 голосов
/ 26 июня 2010

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

В Википедии есть хорошие статьи по теме:

http://en.wikipedia.org/wiki/Dataflow_programming

http://en.wikipedia.org/wiki/Flow-based_programming

Кроме того, он обладает рядом преимуществ, таких как невероятная гибкая конфигурация, многослойность; программист (Компонентный программист) не должен программировать бизнес-логику, это делается на другом этапе (объединение сети обработки).

Знаете ли вы, make - это система потока данных? См. make -j , особенно если у вас многоядерный процессор.

0 голосов
/ 20 января 2015

Самый безопасный подход к разработке новых приложений с многопоточностью - придерживаться правила:

Нет дизайна ниже дизайна.

Что это значит?

Представьте, что вы определили основные строительные блоки вашего приложения. Пусть это будет графический интерфейс, некоторые вычислительные движки. Как правило, когда у вас достаточно большой размер команды, некоторые люди в команде просят «библиотеки», чтобы «делиться кодом» между этими основными строительными блоками. Хотя с самого начала было относительно легко определить правила потоков и совместной работы для основных строительных блоков, все эти усилия в настоящее время находятся под угрозой, поскольку «библиотеки повторного использования кода» будут плохо спроектированы, спроектированы, когда это необходимо, и будут завалены замками и мьютексами, которые "хорошо чувстовать". Эти специальные библиотеки - это дизайн ниже вашего дизайна и основной риск для вашей многопоточной архитектуры.

Что с этим делать?

  • Скажите им, что у вас скорее дублирование кода, чем общий код через границы потоков.
  • Если вы думаете, что проект действительно выиграет от некоторых библиотек, установите правило, что они должны быть свободными от состояния и входящими.
  • Ваш дизайн развивается, и часть этого «общего кода» может быть «перемещена» в дизайне, чтобы стать новым основным строительным блоком вашего приложения.
  • Держитесь подальше от классной мании библиотеки. Некоторые сторонние библиотеки действительно могут сэкономить вам много времени. Но есть также тенденция, что у каждого есть свои "фавориты", которые вряд ли необходимы. И с каждой сторонней библиотекой, которую вы добавляете, повышается риск возникновения проблем с многопоточностью.

Не в последнюю очередь, подумайте о взаимодействии между вашими основными строительными блоками; см., например, часто упоминаемую модель актера.

0 голосов
/ 26 июня 2010

Некоторые эксперты считают, что ответ на ваш вопрос заключается в том, чтобы вообще избежать потоков, потому что избежать непредвиденных проблем практически невозможно. Цитировать Проблема с потоками :

Мы разработали процесс, который включал система оценки зрелости кода (с четырьмя уровнями: красный, желтый, зеленый и синий), обзоры дизайна, код обзоры, ночные сборки, регрессионные тесты и автоматические показатели покрытия кода. Порция ядра, которое обеспечивало согласованное представление о структуре программы, было написано в начале 2000 года, дизайн изменен на желтый, а код - на зеленый. В состав рецензентов входили эксперты по параллелизму, не только неопытные аспиранты (Кристофер Хайландс (ныне Брукс), Барт Киенхуис, Джон Рики и [Эд Ли] были рецензентами). Мы написали регрессионные тесты, которые достигли 100% кода охват ... ... сама система стала широко использоваться, и каждое использование системы осуществляло это код. Никаких проблем не наблюдалось, пока код не зашёл в тупик 26 апреля 2004 года, четыре года спустя.

0 голосов
/ 11 января 2009

Похоже, ваш IOC в некотором роде похож на FBP :-) Было бы здорово, если бы код JavaFBP мог быть тщательно проверен кем-то вроде вас, разбирающимся в искусстве написания поточно-ориентированного кода ... Это на SVN в SourceForge .

0 голосов
/ 03 ноября 2008

Я помню, что был несколько шокирован, обнаружив, что класс Java для synchronizedList не был полностью потокобезопасным, но только условно потокобезопасным. Я все еще мог бы сгореть, если бы не оборачивал свои обращения (итераторы, сеттеры и т. Д.) В синхронизированный блок. Это означает, что я, возможно, заверил свою команду и руководство, что мой код является поточно-ориентированным, но я мог ошибаться. Еще один способ обеспечить безопасность потоков - это инструмент для анализа кода и его прохождения. STP, модель Actor, Erlang и т. Д. - это несколько способов получить последнюю форму гарантии. Возможность надежного обеспечения свойств программы является / станет огромным шагом вперед в программировании.

...