Ремонт принтеров, сканнеров, факсов и остальной офисной техники


назад Оглавление вперед




[93]

Сложности при использовании множественных разделяемых ресурсов

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

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

(define (exchange accountl account2)

(let ((difference (- (accountl balance)

(account2 balance)))) ((accountl withdraw) difference) ((account2 deposit) difference)))

Эта процедура работает правильно в том случае, когда только один процесс пытается осуществить обмен. Допустим, однако, что Петр и Павел имеют доступ к совместным счетам al, a2 и a3, и что Петр меняет местами al и a2, а Павел в то же время обменивает al и a3. Даже если снятие и занесение денег на отдельные счета сериализованы (как в процедуре make-account из предыдущего раздела), exchange может привести к неверным результатам. Например, может оказаться, что Петр посчитает разницу между al и a2, но Павел изменит баланс на al прежде, чем Петр закончит обмен.42 Чтобы добиться правильного поведения, мы должны устроить так, чтобы процедура exchange блокировала всякий параллельный доступ к счетам на все время обмена.

Один из способов этого достичь - сериализовать всю процедуру exchange сериализаторами обоих счетов. Ради этого мы откроем доступ к сериализато-рам счетов. Обратите внимание, что, раскрывая сериализатор, мы намеренно ломаем модульное построение объекта-банковского счета. Следующая версия процедуры make-account идентична исходной версии из раздела 3.1.1, за исключением того, что имеется сериализатор для защиты переменной баланса, и он экспортируется через передачу сообщений:

(define (make-account-and-serializer balance) (define (withdraw amount) (if (>= balance amount)

(begin (set! balance (- balance amount))

balance) "Недостаточно денег на счете")) (define (deposit amount)

(set! balance (+ balance amount))

41 Мы упростили exchange, пользуясь тем, что наше сообщение deposit может принимать отрицательные суммы. (Для банковской системы это серьезная ошибка!)

42Если балансы на счетах вначале равны 10, 20 и 30 долларам, то после любого количества параллельных обменов балансы должны по прежнему быть 10, 20 и 30, в каком-то порядке. Се-риализации доступа к отдельным счетам недостаточно, чтобы это гарантировать. См. упр. 3.43.


balance)

(let ((balance-serializer (make-serializer))) (define (dispatch m)

(cond ((eq? m withdraw) withdraw) ((eq? m deposit) deposit) ((eq? m balance) balance) ((eq? m serializer) balance-serializer) (else (error "Неизвестный запрос -- MAKE-ACCOUNT"

m))))

dispatch))

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

(define (deposit account amount) (let ((s (account serializer)) (d (account deposit))) ((s d) amount)))

Экспорт сериализатора дает нам достаточно гибкости, чтобы реализовать се-риализованную программу обмена. Мы просто-напросто сериализуем исходную процедуру exchange сериализаторами обоих счетов:

(define (serialized-exchange account1 account2) (let ((serializer1 (account1 serializer)) (serializer2 (account2 serializer))) ((serializer1 (serializer2 exchange)) account1 account2)))

Упражнение 3.43.

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

Упражнение 3.44.

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

43 В упражнении 3.45 рассматривается вопрос, почему занесение и снятие денег теперь не сериа-лизуются счетом автоматически.


(define (transfer from-account to-account amount) ((from-account withdraw) amount) ((to-account deposit) amount))

Хьюго Дум считает, что с этой версией возникнут проблемы и что нужно использовать более сложный подход, вроде того, который требуется при решении задачи обмена. Прав ли он? Если нет, то в чем состоит существенная разница между задачей перевода денег и задачей обмена счетов? (Нужно предположить, что значение баланса на from-account по крайней мере равно amount.)

Упражнение 3.45.

Хьюго Дум полагает, что теперь, когда операции снятия денег со счета и занесения их на счет перестали сериализовываться автоматически, система банковских счетов стала неоправданно сложной и работать с ней правильным образом чересчур трудно. Он предлагает сделать так, чтобы make-account-and-serializer экспортировал сери-ализатор (для использования в процедурах вроде serialized-exchange), и вдобавок сам использовал его для сериализации простых операций со счетом, как это делал make-account. Он предлагает переопределить объект-счет так:

(define (make-account-and-serializer balance) (define (withdraw amount) (if (>= balance amount)

(begin (set! balance (- balance amount))

balance) "Недостаточно денег на счете")) (define (deposit amount)

(set! balance (+ balance amount)) balance)

(let ((balance-serializer (make-serializer))) (define (dispatch m)

(cond ((eq? m withdraw) (balance-serializer withdraw)) ((eq? m deposit) (balance-serializer deposit)) ((eq? m balance) balance) ((eq? m serializer) balance-serializer) (else (error "Неизвестный запрос -- MAKE-ACCOUNT"

m))))

dispatch))

(define (deposit account amount) ((account deposit) amount))

Объясните, в чем Хьюго ошибается. В частности, рассмотрите, что происходит при вызове serialized-exchange.

Реализация сериализаторов

Мы реализуем сериализаторы на основе более примитивного механизма синхронизации, называемого мьютекс (mutex). Мьютекс - это объект, который поддерживает две операции: его можно захватить (acquire), и его можно освободить (release). Когда мьютекс захвачен, никакая другая операция захвата того же самого мьютекса произойти не может, пока его не освободят.44 В нашей

44Название «мьютекс» происходит от английского mutual exclusion «взаимное исключение». Общая проблема построения механизма, который позволил бы параллельным процессам безопасно разделять ресурсы, называется проблемой взаимного исключения. Наши мьютексы являются про-



[стр.Начало] [стр.1] [стр.2] [стр.3] [стр.4] [стр.5] [стр.6] [стр.7] [стр.8] [стр.9] [стр.10] [стр.11] [стр.12] [стр.13] [стр.14] [стр.15] [стр.16] [стр.17] [стр.18] [стр.19] [стр.20] [стр.21] [стр.22] [стр.23] [стр.24] [стр.25] [стр.26] [стр.27] [стр.28] [стр.29] [стр.30] [стр.31] [стр.32] [стр.33] [стр.34] [стр.35] [стр.36] [стр.37] [стр.38] [стр.39] [стр.40] [стр.41] [стр.42] [стр.43] [стр.44] [стр.45] [стр.46] [стр.47] [стр.48] [стр.49] [стр.50] [стр.51] [стр.52] [стр.53] [стр.54] [стр.55] [стр.56] [стр.57] [стр.58] [стр.59] [стр.60] [стр.61] [стр.62] [стр.63] [стр.64] [стр.65] [стр.66] [стр.67] [стр.68] [стр.69] [стр.70] [стр.71] [стр.72] [стр.73] [стр.74] [стр.75] [стр.76] [стр.77] [стр.78] [стр.79] [стр.80] [стр.81] [стр.82] [стр.83] [стр.84] [стр.85] [стр.86] [стр.87] [стр.88] [стр.89] [стр.90] [стр.91] [стр.92] [стр.93] [стр.94] [стр.95] [стр.96] [стр.97] [стр.98] [стр.99] [стр.100] [стр.101] [стр.102] [стр.103] [стр.104] [стр.105] [стр.106] [стр.107] [стр.108] [стр.109] [стр.110] [стр.111] [стр.112] [стр.113] [стр.114] [стр.115] [стр.116] [стр.117] [стр.118] [стр.119] [стр.120] [стр.121] [стр.122] [стр.123] [стр.124] [стр.125] [стр.126] [стр.127] [стр.128] [стр.129] [стр.130] [стр.131] [стр.132] [стр.133] [стр.134] [стр.135] [стр.136] [стр.137] [стр.138] [стр.139] [стр.140] [стр.141] [стр.142] [стр.143] [стр.144] [стр.145] [стр.146] [стр.147] [стр.148] [стр.149] [стр.150] [стр.151] [стр.152] [стр.153] [стр.154] [стр.155] [стр.156] [стр.157] [стр.158] [стр.159] [стр.160] [стр.161] [стр.162] [стр.163] [стр.164] [стр.165] [стр.166] [стр.167] [стр.168] [стр.169] [стр.170] [стр.171] [стр.172] [стр.173] [стр.174] [стр.175] [стр.176] [стр.177] [стр.178] [стр.179] [стр.180] [стр.181] [стр.182] [стр.183] [стр.184] [стр.185] [стр.186] [стр.187] [стр.188] [стр.189] [стр.190] [стр.191] [стр.192] [стр.193] [стр.194] [стр.195] [стр.196]