Объект std::unique_lock
отлично приспособлен для таких ситуаций, потому что можно вызвать его метод unlock()
, когда программе не нужен доступ к разделяемым данным, а затем вызвать lock()
, если доступ снова понадобится:
void get_and_process_data()
(1) Во время работы process() зах-
{ ←┘
ватывать мьютекс не нужно
std::unique_lock
some_class data_to_process = get_next_data_chunk();
my_lock.unlock();
result_type result = process(data_to_process);
my_lock.lock(); ←┐
Снова захватить мью-
write_result(data_to_process, result);│
текс перед записью
}
(2) результатов
Удерживать мьютекс на время выполнения process()
нет необходимости, поэтому мы вручную освобождаем его перед вызовом (1) и снова захватываем после возврата (2).
Очевидно, что если один мьютекс защищает структуру данных целиком, то не только возрастает конкуренция за него, но и шансов снизить время удержания остается меньше. Поскольку под защитой одного мьютекса приходится выполнять больше операций, то и удерживать его нужно дольше. Такая двойная угроза должна вдвое усилить стремление всюду, где возможно, использовать мелкогранулярные блокировки.
Как следует из примера, выбор подходящей гранулярности определяется не только объемом защищаемых данных, но и временем удержания блокировки и тем, какие операции выполняются под ее защитой.
В листингах 3.6 и 3.9 мы захватывали два мьютекса для операции обмела, которая очевидно требует одновременного доступа к обоим объектам. Предположим, однако, что требуется произвести сравнение простых членов данных типа int
. В чем разница? Копирование целых чисел — дешевая операция, поэтому вполне можно было бы скопировать данные из каждого объекта под защитой мьютекса, а затем сравнить копии. Тогда мьютекс удерживался бы минимальное время, и к тому же не пришлось бы захватывать новый мьютекс, когда один уже удерживается. В следующем листинге показам как раз такой класс Y
и пример реализации в нем оператора сравнения на равенство.
Листинг 3.10. Поочерёдный захват мьютексов в операторе сравнения
class Y {
private:
int some_detail;
mutable std::mutex m;
int get_detail() const {
std::lock_guard
(1)
return some_detail;
}
public:
Y(int sd): some_detail(sd) {}
friend bool operator==(Y const& lhs, Y const& rhs) {
if (&lhs == &rhs)
return true;
int const lhs_value = lhs.get_detail(); ←
(2)
int const rhs_value = rhs.get_detail(); ←
(3)
return lhs_value == rhs_value; ←
(4)
}
};