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

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

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

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

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

Ответы [ 15 ]

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

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

Проектируйте потоки так же, как вы разрабатываете классы: убедитесь, что знаете, что делает их согласованными: их содержимое и их взаимодействие с другими потоками.

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

Актерская модель - это то, что вы используете, и это, безусловно, самый простой (и эффективный) способ многопоточности. По сути, каждый поток имеет (синхронизированную) очередь (она может зависеть от ОС или нет), а другие потоки генерируют сообщения и помещают их в очередь потока, который будет обрабатывать сообщение.

Базовый пример:

thread1_proc() {

msg = get_queue1_msg(); // block until message is put to queue1
threat1_msg(msg);

}

thread2_proc() {
msg = create_msg_for_thread1();
send_to_queue1(msg);
}

Это типичный пример производителя-потребителя проблемы.

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

Предлагаю модель актера.

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

Основные проблемы, которые я видел, заключались в том, чтобы (а) избежать тупиков и (б) обмениваться данными между потоками. Беспокойство арендодателя (но только немного меньше) - избегать узких мест. Я уже сталкивался с несколькими проблемами с разрозненной блокировкой вне последовательности, вызывающей взаимные блокировки - очень хорошо сказать «всегда получать блокировки в одном и том же порядке», но в средних и больших системах практически невозможно обеспечить это.

Предостережение: когда я придумал это решение, мне пришлось ориентироваться на Java 1.1 (поэтому пакет для параллелизма еще не был мерцанием в глазах Дуга Ли) - имеющиеся инструменты были полностью синхронизированы и ждали / уведомляли. Я опирался на опыт написания сложной многопроцессорной системы связи с использованием системы сообщений в реальном времени QNX.

Основываясь на своем опыте работы с QNX, который имел проблему тупиковой ситуации, но избегал параллелизма данных, копируя сообщения из пространства памяти одного процесса в другое, я придумал основанный на сообщениях подход для объектов - который я назвал IOC, для интер -объект координации. На начальном этапе, который я предусмотрел, я мог бы создать все моих объектов подобным образом, но в ретроспективе выясняется, что они необходимы только в основных контрольных точках в большом приложении - «межгосударственных развязках», если вы будете не подходит для каждого «перекрестка» в дорожной системе. Это оказывается главным преимуществом, потому что они совершенно не POJO.

Я предполагал систему, в которой объекты не будут концептуально вызывать синхронизированные методы, а вместо этого будут "отправлять сообщения". Сообщения могут быть отправкой / ответом, когда отправитель ждет, пока сообщение обрабатывается и возвращается с ответом, или асинхронным, когда сообщение отбрасывается в очередь, снимается с очереди и обрабатывается на более позднем этапе. Обратите внимание, что это концептуальное различие - обмен сообщениями был реализован с использованием синхронизированных вызовов методов.

Основными объектами для системы обмена сообщениями являются IsolatedObject, IocBinding и IocTarget.

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

Object processIocMessage(Object msgsdr, int msgidn, Object msgdta)

и все остальные методы являются частными для обработки определенных сообщений.

IocTarget является средством абстрагирования видимости изолированного объекта и очень полезен для придания другому объекту собственной ссылки для отправки вам сигналов назад, без раскрытия фактической ссылки на объект.

И IocBinding просто привязывает объект отправителя к получателю сообщения, чтобы проверки проверки не выполнялись для каждого отправленного сообщения, а создавался с использованием IocTarget.

Все взаимодействие с изолированными объектами происходит через «отправку» им сообщений - метод processIocMessage получателя синхронизируется, что обеспечивает одновременную обработку только одного сообщения.

Object iocMessage(int mid, Object dta)
void   iocSignal (int mid, Object dta)

Создав ситуацию, когда вся работа, выполняемая изолированным объектом, направляется одним способом, я затем расположил объекты в объявленной иерархии посредством «классификации», которую они объявляют при построении - просто строка, которая идентифицирует их как будучи одним из любого числа «типов получателей сообщений», который помещает объект в некоторую заранее определенную иерархию. Затем я использовал код доставки сообщения, чтобы убедиться, что если отправитель сам был изолированным объектом, то для синхронных сообщений отправки / ответа это был тот, который ниже в иерархии. Асинхронные сообщения (сигналы) отправляются получателям сообщений, используя отдельные потоки в пуле потоков, которые всю работу доставляют сигналы, поэтому сигналы могут быть отправлены из любого объекта любому получателю в системе. Сигналы могут доставить любые данные сообщения, но ответ невозможен.

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

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

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

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

Весь проект становится одной из упорядоченной совокупности подсистем , взаимодействующих строго контролируемым образом.

Обратите внимание, что это не используется для более простых ситуаций, когда рабочих потоков, использующих более обычные пулы потоков, будет достаточно (хотя я часто вставляю результаты работника обратно в основную систему, отправляя сообщение IOC). Он также не используется в ситуациях, когда поток отключается и делает что-то полностью независимое от остальной системы, например поток HTTP-сервера. Наконец, он не используется в ситуациях, когда существует координатор ресурсов, который сам не взаимодействует с другими объектами и где внутренняя синхронизация будет выполнять работу без риска тупика.

РЕДАКТИРОВАТЬ: я должен был сказать, что сообщения, которыми обмениваются, как правило, должны быть неизменными объектами; если используются изменяемые объекты, то акт отправки должен рассматриваться как передача, и отправитель должен полностью отказаться от контроля и, предпочтительно, не сохранять никаких ссылок на данные. Лично я использую блокируемую структуру данных, которая блокируется кодом IOC и, следовательно, становится неизменной при отправке (флаг блокировки является энергозависимым).

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

Написание всего кода в многопоточном приложении очень ... аккуратно! Я не знаю лучшего ответа, чем этот. (Это касается таких вещей, как jonnii , упомянутых).

Я слышал, что люди утверждают (и соглашаются с ними), что традиционная модель многопоточности действительно не сработает в будущем, поэтому нам нужно будет разработать другой набор парадигм / языков, чтобы реально использовать их эффективно новомодные многоядерные. Такие языки, как Haskell, чьи программы легко распараллеливаются, так как любая функция, имеющая побочные эффекты, должна быть явно помечена таким образом, и Erlang, о котором, к сожалению, я не знаю так много.

...