Разделяемые
ресурсы
В современном
операционном окружении программист не может быть уверен и не должен полагаться
на то, что коды его программы будут выполняться в тон же последовательности,
в какой они написаны. Выполнение одной из функций программы может быть остановлено
системой и возобновлено позднее, причем это может произойти даже при выполнении
тела какого-либо цикла. При проектировании многопотоковых приложений следует
иметь в виду, что ресурсы, разделяемые потоками (блоки памяти или файлы), можно
неосознанно повредить. Чтобы показать, как это происходит, рассмотрим пример,
который приведен в книге Jesse Liberty «Beginning Object-Oriented Analysis and
Design with C++» (Дж. Либерти «Начало объектно-ориентированного анализа и проектирования
с помощью C++»), доступной в MSDN.
Представьте
себе пассажирский авиалайнер в полете, а в нем такой разделяемый всеми ресурс,
как туалетная комната. Создатели самолета предполагали, что только одна персона
может занимать эту комнату. Первый, кто ее занял, закрывает (lock) доступ к
пей для всех остальных. Следующий пассажир, желающий воспользоваться этим ресурсом,
может либо терпеливо ожидать освобождения, либо по истечении какого-то времени
(time out) вернуться на свое сиденье и продолжать заниматься тем, чем он был
занят до этого события. Решение о том, что выбрать и как долго ждать, принимает
пассажир. Блокирование ресурса порождает неэффективное проведение времени второго
пассажира как ожидающего очереди, так и избравшего другую тактику.
Возвращаясь
к многопотоковым процессам, отметим, что если не блокировать ресурс,
то становится возможным повреждение данных. Представьте, что один поток процесса
проходит по записям базы данных, повышая зарплату каждому сотруднику на 10%,
а другой поток в это же время изменяет почтовые индексы в связи с введением
нового стандарта. Согласитесь с тем, что разумно совместить эти две работы в
одном процессе с целью повышения производительности. Что может произойти, если
не блокировать доступ к записи при ее модификации? Первый поток прочел запись
(все ее поля), и занят вычислением повышения (предположим, с $80 000 до $85
000). В это время второй поток читает эту же запись с целью изменения почтового
индекса. В этой ситуации может произойти следующее: первый поток сохраняет измененную
запись с новым значением зарплаты, а второй, возвращая запись с измененным индексом,
реставрирует значение зарплаты и данный сотрудник останется без повышения. Это
происходит по причине того, что оба потока не могут обратиться к части записи
и поэтому работают со всей записью, хотя модифицируют только отдельные ее поля.