пятница, июля 27, 2007

SysAdmin day

Сегодня последняя пятница июля - SysAdmin Day. C праздником коллеги и друзья!

Ночной дозор, или баллада о системном администраторе.



1.
Опять сгорает за окном
Прошедший день в лучах заката.
И на прощание объята
Лазурь листвы его огнём.

А ты глядишь на свой экран.
Бежит строка, за словом слово,
Мелькают мысли, снова, снова
И разгоняют снов туман.

И ночь вошла в твои покои,
Чуть слышным шепотом берез.
Прохлада ветра, запах роз -
- Мир грёз. Бывает же такое.

А может нет? Там гул машин
Не прекращается веками
И затянуло облаками
Небес мерцающий сатин.

Но это там, а здесь лишь ты
И полусонные мечты.

2.
Мелькают ссылки, адреса
Уже знакомые до боли,
И лишь незримые пароли
Не жгут усталые глаза.

За то их точно помнят руки,
Рождая танец. Каждый миг
Переливается твой
Цветком, незнающим разлуки.

Ты полубог своей системы,
Её и правишь, и кроишь,
А в такт пощелкивает <мышь>,
И разрешаются проблемы.

Бодрящий кофе аромат
Перед экраном монитора.
И мир, в бескрайности простора
Раскрыл пред взором сотни врат.

Но кто же ты? Узнать изволь,
Твоей души логин, пароль.

3.
Зачем сидишь, скажи на милость?
Неужто ночью не до сна?
И кофе выпито до дна.
И голова почти склонилась:

Но как бы не слипались веки.
В лучах ЭЛТ, или ЖК
Ты <улетаешь> на века
И забываешься на веки

В кибернетической цепи,
Среди мерцания диодов,
Лавины импульсов и кодов,
Перекликания .

И вот, с невидимой рекой
Поток проходит через <шлюзы>.
объединяет узы
Кибернетической рукой.

Сквозь магистралей волокно
Летит <привет> в твоё <окно>.

4.
Природа спит, во мраке ночи.
Горят рядами фонари.
Всё дожидается зори.
И ты зеваешь, между прочим.

А что в сети? Там тоже штиль.
И, на глазах редеет <чат>.
<Порты свободные> молчат.
И тишина на сотни миль.

Невозмутимый океан
Всерастворяющей тиши
Уже наполнил мир души
И потушил цветной экран.

Но лишь струится в кулера,
Машин спокойное дыханье,
И электронное сознанье
Еще хранят процессора.

Огромный безмятежный мир
Пронзает радиоэфир.


c Copyright: Вьюга(Z-admin), 2005

Ключевой сотрудник.

Многие часто жалуются на то, что получить прибавку к зарплате очень сложно. Несмотря на то, что зарплаты постоянно растут, работодатели не спешат поднимать зарплаты своим сотрудникам с той же скоростью. Тогда почему же они растут? Когда компании нужен новый человек, найти его, предлагая зарплату среднюю по рынку, довольно затруднительно, особенно в условиях кадрового дефицита, как сейчас. То есть компании вынуждены предлагать зарплату выше среднего, если им нужен квалифицированный специалист, либо когда человек нужен срочно. Это разогревает рынок зарплат. Иной раз складывается парадоксальная ситуация, когда только что принятый сотрудник получает больше чем тот, который проработал год и на более высокой должности.

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

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

Поднимать зарплату за чтение умных книжек? За полученный сертификат? Дудки!!! Это вам скажет любой работодатель.

Зарплату поднимают за степень полезности - важности сотрудника.

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

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

Есть правда, такой тип руководителей (и контор) которые дают тебе работать (если до работы жадный), а платить за это не любят. Приходишь на новое место, зарплата небольшая но, как говорится, с перспективой роста. Отработал год, результаты отличные. Все тебе говорят:"Молодец, Федя, так держать!", а в расчетном листке это никак не отражается. Тут конечно, без вопросов, новую работу надо подыскивать.

четверг, июля 26, 2007

Программное обеспечение и параллельная революция (Часть 5)

Херб Саттер (Herb Sutter) и Джэймс Лэрус (James Larus), Microsoft.

Часть 5. Потребности в инструментах

Что нам надо среди инструментов

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

Параллелизм порождает новые типы программных ошибок, в дополнение к тем, что хорошо знакомы нам по обычному программированию. Гонки данных (data races (в результате недостаточной синхронизации и клинчей), активные тупики (livelocks) ( в результате излишней синхронизации) представляют собой дефекты, сложные для обнаружения и понимания, поскольку их поведение неопределенно и трудно воспроизводимо. Обычные методы отладки, такие как повторное исполнение с предварительно установленными точками останова не работают для параллельных программ, чьи пути исполнения и поведение могут изменяться от прогона к прогону.

Инструменты для систематического выявления дефектов необычайно важны в этом мире. Такие инструменты используют статический анализ кода программы для систематического выявления всех возможных путей исполнения, таким образом, они способны обнаружить ошибки, которые невозможно воспроизвести. Хотя подобные методики, такие как верификация модели, с большим успехом используются для обнаружения дефектов в аппаратном обеспечении, которому тоже присущ параллелизм, с программным обеспечением все сложнее. Количество возможных состояний любой программы гораздо больше, чем это характерно для большинства hardware, поэтому необходимо гораздо больше работы для систематического анализа состояний программы. В обоих случаях, модульность и абстракция являются ключевыми моментами для упрощения анализа. Если при тестировании модели процессора мы можем выделить АЛУ (арифметико-логическое устройство) и анализировать его отдельно от набора регистров, то задача анализа существенно упрощается.

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

Инструменты для выявления дефектов в параллельном ПО являют собой область для активных исследований. Одна из перспективных методик от Microsoft Research под названием KISS (Keep it Strictly Sequential) (4) преобразует многопоточную программу в последовательную, которая включает все возможные чередования оригинальных потоков. Преобразованная таким образом программа может быть проанализирована большим количеством существующих инструментов для анализа последовательных программ.

Даже продвинутые программисты все же нуждаются в хороших отладчиках, которые позволили бы им разобраться в запутанных и сложных для воспроизведения взаимодействиях в их параллельных программах. Существует две основные методики для сбора этой информации. Первая, это улучшение возможностей логирования, включая отслеживание сообщений передаваемых процессам и того, какие потоки обращаются к каким данным, что позволило бы разработчику отследить и понять порядок исполнения в своей программе. Разработчики также нуждаются в возможностях отслеживать причинно следственные связи между потоками (например, какие сообщения направлены одному активному объекту, когда обработаны, как связаны с другими сообщениями к другим активным объектам?), перезаписи и изменения порядка сообщений в очередях, пошагового исполнения асинхронных вызовов включая обратные вызовы (callbackы), и прочие возможности исследования параллельного исполнения своих программ. Другой подход, это обратное исполнение кода, которое позволяет программисту записать ход исполнения программы и затем повторно «проиграть» этот код. Replay debugging не является новой идеей, но ее реализации мешали высокая сложность и ресурсоемкость. Появившиеся недавно мониторы виртуальных машин (5) (virtual machine monitors) способны снизить оба фактора. В параллельном мире эта методика, вероятно, окажется востребованной.

Нагрузочная отладка и оптимизация также потребуют новых инструментов в параллельном мире. Параллелизм порождает новые узкие места, такие как конфликты блокировок, издержки на поддержание когерентности кэша (проблема false sharing, например. Примечание переводчика), “lock convoys”, которые трудно отследить при помощи обычных профилировщиков. Новые инструменты, более приспособленные к конкретным компьютерным архитектурам и к особенностям параллельных программ, будут более эффективны при обнаружении подобных проблем.

Тестирование также должно измениться. Параллельные программы из-за своего непредсказуемого поведения более сложны в тестировании. Простые метрики покрытия кода, которые отслеживают код, исполняемый в тестах, должны быть расширены для подсчета кода исполняемого параллельно, иначе тесты будут давать неоправданно оптимистическую картину того, насколько полно протестирован код. Кроме того, простое стрессовое тестирование должно быть дополнено более систематическими методами, которые используют верификацию моделей для исследования возможных состояний системы. Например, Verisoft достиг больших успехов в использовании подобных методик при поиске ошибок в параллельном ПО для телефонных станций (6). Сегодня многие параллельные приложения используют длительные нагрузочные тесты для того, чтобы снизить вероятность возникновения в приложении неконсистентных данных. В будущем, конечно, такой подход станет не приемлем. Разработчики будут нуждаться в проверке качества ПО на основе строгих детерминированных тестов, а не вероятностных характеристик на основе нагрузочного тестирования.

Ключ в параллелизме

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

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

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

Ссылки

1. Sutter, H. 2005. The free lunch is over: a fundamental turn toward concurrency in software. Dr. Dobb’s Journal 30 (3); http://www.gotw.ca/publications/concurrency-ddj.htm.
2. Ramalingam, G. 2000. Context-sensitive synchronization-sensitive analysis is undecidable. ACM Transactions on Programming Languages and Systems 22 (2): 416-430.
3. Dean, J., and Ghemawat, S. 2004. MapReduce: simplified data processing on large clusters. Proceedings of the Sixth Symposium on Operating Systems Design and Implementation, San Francisco, CA: 137-150.
4. Qadeer, S., and Wu, D. 2004. KISS: Keep it Simple and Sequential. Proceedings of the ACM SIGPLAN 2004 Conference on Programming Language Design and Implementation, Washington, DC: 1-13.
5. King, S. T., Dunlap, G. W., and Chen, P. M. 2005. Debugging operating systems with time-traveling virtual machines. Proceedings of the 2005 Annual Usenix Technical Conference, Anaheim, CA: 1-15.
6. Chandra, S., Godefroid, P., and Palm, C. 2002. Software model checking in practice: an industrial case study. Proceedings of the 24th International Conference on Software Engineering, Orlando, FL: 431-441.

Программное обеспечение и параллельная революция (Часть 4)

Херб Саттер (Herb Sutter) и Джэймс Лэрус (James Larus), Microsoft.

Часть 4. Потребности в языках

Что нам надо от программных языков

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

Явная, неявная и автоматическая параллелизация

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

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

Другой широко обсуждаемый подход, это автоматическое распараллеливание, когда компилятор пытается обнаружить параллелизм в программах, написанных на обычных языках, таких как ФОРТРАН. Несмотря на то, что данный подход выглядит очень привлекательно, на практике он работает не так хорошо, как ожидалось. Для понимания возможного поведения программы необходим точный анализ. Этот анализ достаточно сложен для таких простых языков как ФОРТРАН, и на порядки сложнее для таких языков как С, манипулирующих структурами данных на указателях. Кроме того, последовательные программы обычно используют последовательные алгоритмы и содержат мало параллелизма.

Императивные и функциональные языки

Популярные коммерческие языки программирования (такие как, Pascal, C, C++, Java, C#) являются императивными языками, в которых программист шаг за шагом описывает изменения, производимые над переменными и структурами данных. Мелкозернистые управляющие конструкции (такие как циклы), низкоуровневое манипулирование данными, разделяемые, изменяемые (shared mutable) экземпляры объектов делают программы на таких языках сложными для автоматического распараллеливания.

Существует популярная точка зрения, что функциональные языки, такие как Scheme, ML или Haskell могли бы устранить эти препятствия, потому что они естественным образом подходят для параллельного исполнения. Программы, написанные на этих языках, манипулируют неизменяемыми (immutable) экземплярами объектов, которые не подвержены угрозам конкурентного доступа. Кроме того, в отсутствии побочных эффектов (Примечание переводчика, side effects – эффекты изменения глобальных переменных после выполнения подпрограммы.) кажется, что программа имеет меньше ограничений на порядок исполнения.

Однако на практике, функциональные языки не обязательно способствуют параллелизму. В программах на функциональных языках, параллелизм обычно возникает на уроне вызовов процедур, что не очень удобно для обычных параллельных процессоров. Коме того, большинство функциональных языков допускают наличие побочных эффектов (side effects) в изменяемых данных (mutable state), и код, использующий эти возможности, весьма сложен для автоматического распараллеливания.

Эти языки вводят понятие «изменяемого состояния» (mutable state) в целях выразительности и эффективности. В чисто функциональном языке, составные структуры данных, такие как массивы или деревья, обновляются путем создания копии, содержащей измененное значение. Эта техника привлекательна с точки зрения семантики, но может быть ужасна с точки зрения производительности (линейный алгоритм с легкостью превращается в квадратичный). Также, функциональные языки никак не препятствуют написанию чисто последовательных алгоритмов, где каждая операция ожидает, пока предыдущая обновляет состояние программы.

Реальный вклад функциональных языков в параллелизм состоит в повсеместном использовании на высоком уровне особого стиля программирования, при котором операции, такие как map или map-reduce, применяются одновременно ко всем элементам составных структур данных. Такие высокоуровневые операции – хороший источник параллелизма. Этот стиль программирования, к счастью, присущ не только чистым функциональным языкам, но также может быть использован в императивных программах.
К примеру, Google Fellows Jeffrey Dean и Sanjay Ghemawat рассказывают как Google использует Map-Reduce для выполнения масштабируемых, высокопроизводительных вычислений (3). Императивные языки могут благоразумно добавлять функциональные расширения и получать от этого существенные выгоды. Это важно, потому что индустрия не может просто начать все с начала. Постепенное добавление поддержки параллельных возможностей очень важно для сохранения огромных инвестиций в существующие приложения, пока существует задел опыта и навыков разработчиков в императивных языках.

Лучшие абстракции

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

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

Два базовых примера таких высокоуровневых абстракций это асинхронные вызовы (asynchronous calls) и фьючерсы (futures). Асинхронный вызов, это функция или метод, вызов которой происходит в неблокирующем режиме. Вызывающий код продолжает исполняться, а соответствующее сообщение посылается задаче или вилке (task or fork) на выполнение операции независимо. Фьючерс, это механизм для получения результата от асинхронного вызова, это заместитель (placeholder) для значения, которое еще не материализовалось.

Другой пример абстракции высокого уровня, это активный объект, который концептуально исполняется в своем собственном потоке, таким образом, что создав 1000 таких объектов мы создаем 1000 потенциальных потоков исполнения. Активный объект ведет себя как монитор, в котором только один метод может исполняться в данное время, но он не требует для этого использования традиционных блокировок. Вместо этого, внешние вызовы методов активного объекта представляют собой асинхронные сообщения, которые маршализуются и ставятся в очередь на обработку объектом. Активные объекты могут иметь самый разнообразный дизайн, от специализированных языковых конструкций до COM объектов в модели single-threaded apartments, вызываемого из традиционного кода на C.

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

среда, июля 25, 2007

Программное обеспечение и параллельная революция (Часть 3)

Херб Саттер (Herb Sutter) и Джэймс Лэрус (James Larus), Microsoft.

Часть 3. Блокировки

Проблема разделяемого состояния, и почему блокировки не являются решением

Другим сложным аспектом неструктурированного параллелизма являются разделяемые данные. Клиентское приложение обычно манипулирует разделяемой памятью, организованной как связанный непредсказуемым образом граф объектов.
Когда две задачи пытаются получить доступ к одному объекту, и одна из них желает изменить его состояние, мы попадаем в ситуацию гонки данных (data race). Гонки это плохо, потому что параллельные задачи могут записать и прочитать неконсистентные или поврежденные данные.

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

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

Проблема композирования вызвана главным образом возможностью наступления клинча (deadlock). Простейший вариант клинча наступает, когда две задачи пытаются получить две блокировки во встречном порядке: задача Т1 получает блокировку L1, задача T2 получает блокировку L2, затем, задача T1 пытается получить блокировку L2, а задача T2 пытается получить блокировку L1. Обе задачи блокируются. Поскольку такая ситуация может произойти в любой момент времени, и две блокировки могут быть вызваны во встречном порядке, вы не можете контролировать когда удерживаемая в вашем коде блокировка может стать причиной клинча.

Именно это постоянно происходит в расширяемых средах (frameworks), когда они вызывают виртуальные функции удерживая в это время блокировки (Прим. переводчика. Имеется ввиду, что в переопределенной в наследнике виртуальной функции может быть попытка взять ту же самую блокировку и это станет причиной клинча). Все лучшие на сегодня коммерческие среды не лишены этого недостатка, включая .Net Framework и стандартные библиотеки Java. Мы до сих пор не выбросили их на свалку лишь потому, что разработчики еще не начали писать на них большого количества параллельных приложений. Для решения проблемы клинчей предлагалось множество сложных моделей, например backoff-and-retry протокол, но все они требуют строгой дисциплины при кодировании, а некоторые порождают собственные проблемы (напр., livelocks (активные тупики)).

Техника, позволяющая избегать клинчей, состоит в организации получения блокировок в безопасном порядке не зависимо от того, где они получаются. Например, выравнивание блокировок (lock leveling) и иерархии блокировок (lock hierarchies) препятствуют неправильному порядку вызова блокировок, путем требования того, чтобы все блокировки одного уровня вызывались одновременно в заранее определенном порядке, и в течении времени пока удерживаются блокировки одного уровня вы можете получить дополнительную блокировку только на более высоком уровне. Такая техника хорошо работает внутри одного модуля, или пакета, созданного для внутреннего использования, когда вы можете контролировать весь исходный код. Но ее использование сильно ограничено для расширяемых сред, систем с подключаемыми модулями, и в других подобных случаях, когда система компонуется из кода различных компонент.

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

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

Но существует по крайней мере три проблемы, связанные с синхронизированными методами. Первое, они не подходят для типов, методы которых вызывают виртуальные методы других объектов (напр., Vector в Java, или SyncHashTable в .Net), потому что вызов стороннего кода, в то время как удерживается блокировка, открывает возможность появления клинча. Второе, синхронизированные методы могут выполнять слишком много блокировок, запрашивая их для всех экземпляров объектов, несмотря на то, что большинство из них никогда не разделяют своего состояния между несколькими потоками. Третье, синхронизированные методы могут выполнять слишком короткие блокировки, которые не обеспечивают атомарного изменения состояния при вызове нескольких методов объекта или нескольких объектов. В качестве простого примера, иллюстрирующего последнее утверждение, рассмотрим банковский перевод:

account1.Credit(amount); account2.Debit(amount);

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

Альтернативы блокировкам

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

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