Вход/Регистрация
Эффективное использование STL
вернуться

Мейерс Скотт

Шрифт:

vector<int> v;

…

vector<int>::iterator first5(find(v.begin, v.end, 5)); // Строка 1

if (first5 != v.end) { // Строка 2

 *first5 = 0; // Строка 3

}

В многопоточной среде существует вероятность того, что другой поток изменит содержимое

v
сразу же после выполнения строки 1. Если это произойдет, сравнение
first5
с
v.end
в строке 2 становится бессмысленным, поскольку содержимое
v
будет не тем, каким оно было в конце строки 1. Более того, такая проверка может привести к непредсказуемым результатам, поскольку третий поток может перехватить управление между строками 1 и 2 и сделать
first5
недействительным (например, при выполнении вставки вектор может заново выделить память, вследствие чего все итераторы данного вектора станут недействительными. За подробностями о перераспределении памяти обращайтесь к совету 14). Присваивание
*first5
в строке 3 тоже небезопасно, поскольку между строками 2 и 3 другой поток может удалить элемент, на который указывает (или, по крайней мере, указывал раньше) итератор
first5
.

Ни одно из описанных выше решений с блокировкой не решает этих проблем. Вызовы

begin
и
end
в строке 1 сразу возвращают управление, сгенерированные ими итераторы остаются действительными только до конца строки, а
find
тоже возвращает управление в конце строки.

Чтобы этот фрагмент был потоково-безопасным, блокировка

v
должна сохраняться от строки 1 до строки 3. Трудно представить, каким образом реализация STL могла бы автоматически придти к такому выводу. А если учесть, что использование примитивов синхронизации (семафоров, мьютексов [1] и т. д.) обычно сопряжено с относительно высокими затратами, еще труднее представить, каким образом реализация могла бы сделать это без значительного снижения быстродействия по сравнению с программами, которым априорно известно, что в строках 1-3 с
v
будет работать только один программный поток.

1

В среде программистов данный термин (англ. mutex) встречается также в варианте «мутекс». — Примеч. ред.

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

vector<int> v;

…

getMutexFor(v);

vector<int>::iterator first5(find(v.begin, v.end, 5));

if (first5 != v.end) {// Теперь эта строка безопасна

 *first5 = 0; // И эта строка тоже

}

releaseMutexFor(v);

В другом, объектно-ориентированном, решении создается класс

Lock
, который захватывает мьютекс в конструкторе и освобождает его в деструкторе, что сводит к минимуму вероятность вызова
getMutexFor
без парного вызова
releaseMutexFor
. Основа такого класса (точнее, шаблона) выглядит примерно так:

template<typename Container> // Базовый шаблон для классов,

class Lock{ // захватывающих и освобождающих мьютексы

public:// для контейнеров: многие технические

// детали опущены

 Lock(const Containers container) : c(container) {

getMutexFor(с);// Захват мьютекса в конструкторе

 }

 ~Lock {

releaseMutexFor(c); // Освобождение мьютекса в деструкторе

 }

private:

 const Container& с;

Концепция управления жизненным циклом ресурсов (в данном случае — мьютексов) при помощи специализированных классов вроде

Lock
рассматривается в любом серьезном учебнике C++. Попробуйте начать с книги Страуструпа (Stroustrup) «The C++ Programming Language» [7], поскольку именно Страуструп популяризировал эту идиому, однако информацию также можно найти в совете 9 «More Effective C++». Каким бы источником вы ни воспользовались, помните, что приведенный выше класс
Lock
урезан до абсолютного минимума. Полноценная версия содержала бы многочисленные дополнения, не имеющие никакого отношения к STL. Впрочем, несмотря на минимализм, приведенная версия
Lock
вполне может использоваться в рассматриваемом примере:

vector<int> v;

…

{ // Создание нового блока

 Lock<vector<int> > lock(v); // Получение мьютекса

 vector<int>::iterator first5(find(v.begin, v.end, 5));

 if (first5 != v.end) {

*first5 = 0;

 }

} // Закрытие блока с автоматическим

// освобождением мьютекса

Поскольку мьютекс контейнера освобождается в деструкторе

Lock
, важно обеспечить уничтожение
Lock
сразу же после освобождения мьютекса. Для этого мы создаем новый блок, в котором определяется объект
Lock
, и закрываем его, как только надобность в мьютексе отпадает. На первый взгляд кажется, что вызов
releaseMutexFor
попросту заменен необходимостью закрыть блок, но это не совсем так. Если мы забудем создать новый блок для
Lock
, мьютекс все равно будет освобожден, но это может произойти позднее положенного момента — при выходе из внешнего блока. Если забыть о вызове
releaseMutexFor
, мьютекс вообще не освобождается.

  • Читать дальше
  • 1
  • ...
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • ...

Ебукер (ebooker) – онлайн-библиотека на русском языке. Книги доступны онлайн, без утомительной регистрации. Огромный выбор и удобный дизайн, позволяющий читать без проблем. Добавляйте сайт в закладки! Все произведения загружаются пользователями: если считаете, что ваши авторские права нарушены – используйте форму обратной связи.

Полезные ссылки

  • Моя полка

Контакты

  • chitat.ebooker@gmail.com

Подпишитесь на рассылку: