25.
Необходимость синхронизации. Примеры
выполнения параллельных конкурирующих потоков, работающих с общей переменной и
с наборами разделяемых данных.
Пренебрежение
вопросами синхронизации в многопоточной системе может привести к неправильному
решению задачи или даже к краху системы. Рассмотрим, например (рис. 6.1.1),
задачу ведения базы данных клиентов некоторого предприятия. Каждому клиенту
отводится отдельная запись в базе данных, в которой среди прочих полей имеются
поля Заказ и Оплата. Программа, ведущая базу данных, оформлена как единый
процесс, имеющий несколько потоков, в том числе поток А, который заносит в базу
данных информацию о заказах, поступивших от клиентов, и поток В, который
фиксирует в базе данных сведения об оплате клиентами выставленных счетов. Оба
эти потока совместно работают над общим файлом базы данных, используя
однотипные алгоритмы, включающие три шага.
1.
Считать из файла базы данных в буфер запись о клиенте с заданным
идентификатором.
2.
Внести новое значение в поле Заказ (для потока А) или Оплата (для потока В).
3.
Вернуть модифицированную запись в файл базы данных.
Рис. 6.1.1. Возникновение гонок при доступе к разделяемым данным
Обозначим
соответствующие шаги для потока А как Al, A2 и A3, а
для потока В как Bl, B2 и ВЗ. Предположим, что в
некоторый момент поток А обновляет поле Заказ записи о клиенте N. Для этого он
считывает эту запись в свой буфер (шаг А1), модифицирует значение поля Заказ
(шаг А2), но внести запись в базу данных (шаг A3) не успевает, так как его
выполнение прерывается, например, вследствие завершения кванта времени.
Предположим
также, что потоку В также потребовалось внести сведения об оплате относительно
того же клиента N. Когда подходит очередь потока В, он успевает считать запись
в свой буфер (шаг В1) и выполнить обновление поля Оплата (шаг В2), а затем
прерывается. Заметим, что в буфере у потока В находится запись о клиенте N, в
которой поле Заказ имеет прежнее, не измененное значение.
Когда
в очередной раз управление будет передано потоку А, то он, продолжая свою
работу, запишет запись о клиенте N с модифицированным полем Заказ в базу данных
(шаг A3). После прерывания потока А и активизации потока В последний запишет в
базу данных поверх только что обновленной записи о клиенте N свой вариант
записи, в которой обновлено значение поля Оплата. Таким образом, в базе данных
будут зафиксированы сведения о том, что клиент N произвел оплату, но информация
о его заказе окажется потерянной (рис. 6.1.2, а).
Сложность
проблемы синхронизации кроется в нерегулярности возникающих ситуаций. Так, в
предыдущем примере можно представить и другое развитие событий: могла быть
потеряна информация не о заказе, а об оплате (рис. 6.1.2, б) или, напротив, все
исправления были успешно внесены (рис. 6.1.2, в). Все определяется взаимными
скоростями потоков и моментами их прерывания. Поэтому отладка взаимодействующих
потоков является сложной задачей. Ситуации, подобные той, когда два или более
потоков обрабатывают разделяемые данные и конечный результат зависит от
соотношения скоростей потоков, называются гонками.
Рис. 6.1.2. Влияние относительных скоростей потоков на результат решения задачи