Mybrary.info
mybrary.info » Книги » Компьютеры и интернет » Программирование » Программист-прагматик. Путь от подмастерья к мастеру - Хант Эндрю (читать книги онлайн без TXT) 📗

Программист-прагматик. Путь от подмастерья к мастеру - Хант Эндрю (читать книги онлайн без TXT) 📗

Тут можно читать бесплатно Программист-прагматик. Путь от подмастерья к мастеру - Хант Эндрю (читать книги онлайн без TXT) 📗. Жанр: Программирование. Так же Вы можете читать полную версию (весь текст) онлайн без регистрации и SMS на сайте mybrary.info (MYBRARY) или прочесть краткое содержание, предисловие (аннотацию), описание и ознакомиться с отзывами (комментариями) о произведении.
Перейти на страницу:

25

Балансировка ресурсов

"Я привел тебя в этот мир", – сказал бы мой отец, – "я же могу и отправить тебя обратно. Мне это без разницы. Я сделаю еще одного такого, как ты".

Билл Косби, Отцовство

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

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

Подсказка 35: Доводите до конца то, что начинаете

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

void readCustomer(const char *fName, Customer *cRec) {

cFiie = fopen(fName, "r+");

fread(cRec, sizeof(*cRec), 1, cFile);

}

void writeCustomer(Customer *cRec) {

rewind(cFile);

fwrite(cRec, sizeof(*cRec), 1; cFile);

fclose(cFile);

}

void updateCustomer(const char *fName, double newBalance) {

Customer cRec;

readCustomer(fName, &cRec);

cRec.balance = newBalance;

writeCustomer(&cRec);

}

На первый взгляд, подпрограмма updateCustomer выглядит довольно прилично. Похоже, что она реализует нужную нам логику – считывает запись, обновляет баланс и осуществляет запись обратно в файл. Однако, за внешним приличием и скрывается главная проблема. Подпрограммы readCustomer и writeCustomer тесно связаны между собой [27] – они совместно используют глобальную переменную cFile. Подпрограмма readCustomer открывает файл и сохраняет указатель файла в переменной cFile, а подпрограмма writeCustomer использует сохраненный указатель для закрытия файла по окончании работы. Эта глобальная переменная даже не появляется в подпрограмме updateCustomer.

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

void updateCustomer(const char *fName, double newBalance) {

Customer cRec;

readCustomer(fName, &cRec);

if (newBalance >= 0.0) {

   cRec.balance = newBalance;

   writeCustomer(&cRec);

  }

}

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

У этой проблемы есть весьма неудачное решение – использовать специальный оператор выбора в подпрограмме updateCustomer:

void updateCustomer(const char *fName, double newBalance) {

Customer cRec;

readCustomer(fName, &cRec);

if (newBalance»= 0.0) {

  cRec.balance = newBalance;

  writeCustomer(&cRec);

}

else

fclose(cFile);

}

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

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

void readCustomer(FILE *cFile, Customer *cRec) {

fread(cRec, sizeof(*cRec), 1, cFile);

}

void writeCustomer(FILE *cFile, Customer *cRec) {

rewind(cFile);

fwrite(cRec, sizeof(*cRec), 1, cFile);

}

void updateCustomer(const char *fName, double newBalance) {

FILE *cFile;

Customer cRec;

cFile = fopen(fName, "r+"); // ->>>

readCustomer(cFile, &cRec); //

if (newBalance >= 0.0) {  //

  cRec.balance = newBalance; //

  writeCustomer(cFile, &cRec); //

  } //

 Fclose(cFile);   // <<<-

}

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

Вложенное роспределение ресурса

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

1. Освобождайте ресурсы в последовательности, обратной той, в которой происходило их распределение. При этом можно избежать появления «осиротевших» ресурсов, если один из них содержит ссылки на другой.

2. При распределении одного и того же набора ресурсов в различных местах программы необходимо осуществлять эту операцию в одном и том же порядке. Это уменьшает вероятность взаимоблокировки. (Если процесс А требует resource1 и собирается затребовать resource2, тогда как процесс В затребовал resource2 и пытается заполучить resource1, то два процесса окажутся в состоянии вечного ожидания.)

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

Объекты и исключения

Равновесие между распределениями ресурсов и их освобождениями напоминает о равновесии конструктора и деструктора класса. Класс представляет ресурс, конструктор создает конкретный для этого типа ресурса объект, а деструктор удаляет его из вашей области действия.

Если вы программируете на объектно-ориентированном языке, то упаковка ресурсов в классы может принести пользу. Всякий раз, когда вам необходим конкретный тип ресурса, вы создаете экземпляр объекта указанного класса. Если объект выходит из области действия или повторно запрашивается сборщиком мусора, то деструктор объекта освобождает инкапсулированный ресурс.

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

вернуться

27

Опасности, возникающие из-за связанности в программе, обсуждаются в разделе "Несвязанность и закон Деметера".

Перейти на страницу:

Хант Эндрю читать все книги автора по порядку

Хант Эндрю - все книги автора в одном месте читать по порядку полные версии на сайте онлайн библиотеки mybrary.info.


Программист-прагматик. Путь от подмастерья к мастеру отзывы

Отзывы читателей о книге Программист-прагматик. Путь от подмастерья к мастеру, автор: Хант Эндрю. Читайте комментарии и мнения людей о произведении.


Уважаемые читатели и просто посетители нашей библиотеки! Просим Вас придерживаться определенных правил при комментировании литературных произведений.

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

Надеемся на Ваше понимание и благоразумие. С уважением, администратор mybrary.info.


Прокомментировать
Подтвердите что вы не робот:*