•безопасность записи в разные контейнеры. Несколько потоков могут одновременно производить запись в разные контейнеры.
Обращаю ваше внимание: это то, на что вы можете
Многопоточное программирование считается сложной задачей, и многие программисты желают, чтобы реализации STL изначально обеспечивали полную потоковую безопасность. Это избавило бы их от необходимости самостоятельно синхронизировать доступ. Конечно, это было бы очень удобно, однако добиться этой цели очень сложно. Рассмотрим несколько способов реализации полной потоковой безопасности контейнеров:
•блокировка контейнера на время вызова любой функции;
•блокировка контейнера в течение жизненного цикла каждого возвращаемого итератора (например посредством вызова begin или end);
•блокировка контейнера на протяжении работы каждого алгоритма, вызванного для этого контейнера. В действительности это бессмысленно, поскольку, как будет показано в совете 32, алгоритм не располагает средствами идентификации контейнера, с которым он работает. Тем не менее, мы изучим этот вариант — будет поучительно увидеть, почему он в принципе неработоспособен.
Рассмотрим следующий фрагмент, который ищет в vector
vector
vector
if (first5 != v.end()) {// Строка 2
*first5 = 0;// Строка 3
}
В многопоточной среде существует вероятность того, что другой поток изменит содержимое v
v.end
в строке 2 становится бессмысленным, поскольку содержимое v
будет не тем, каким оно было в конце строки 1. Более того, такая проверка может привести к непредсказуемым результатам, поскольку третий поток может перехватить управление между строками 1 и 2 и сделать first5 недействительным (например, при выполнении вставки вектор может заново выделить память, вследствие чего все итераторы данного вектора станут недействительными. За подробностями о перераспределении памяти обращайтесь к совету 14). Присваивание *first5 в строке 3 тоже небезопасно, поскольку между строками 2 и 3 другой поток может удалить элемент, на который указывает (или, по крайней мере, указывал раньше) итератор first5.Ни одно из описанных выше решений с блокировкой не решает этих проблем. Вызовы begin
end
в строке 1 сразу возвращают управление, сгенерированные ими итераторы остаются действительными только до конца строки, а find
тоже возвращает управление в конце строки.Чтобы этот фрагмент был потоково-безопасным, блокировка v
v
будет работать только один программный поток.Понятно, почему в решении проблем многопоточности не стоит полагаться на реализацию STL. Вместо этого в подобных случаях следует самостоятельно синхронизировать доступ. В приведенном примере это может выглядеть так:
vector
getMutexFor(v);
vector
if (first5 != v.end()) {// Теперь эта строка безопасна
*first5 = 0:// И эта строка тоже
}
releaseMutexFor(v);
В другом, объектно-ориентированном, решении создается класс Lock
getMutexFor
без парного вызова releaseMutexFor
. Основа такого класса (точнее, шаблона) выглядит примерно так:template
class Lock{// захватывающих и освобождающих мьютексы
public:// для контейнеров: многие технические
// детали опущены
Lock(const Containers container)
:c(container)
{
getMutexFor(с);// Захват мьютекса в конструкторе
}
~Lock () {
releaseMutexFor(c): // Освобождение мьютекса в деструкторе
}
private:
const Container& с;