Параллельное программирование на С++ в действии. Практика разработки многопоточных программ | страница 64



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

В листингах 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 lock_a(m); ←(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)

> }

>};

В данном случае оператор сравнения сначала получает сравниваемые значения, вызывая функцию-член >get_detail()(2), (3). Эта функция извлекает значение, находясь под защитой мьютекса (1). После этого оператор сравнивает полученные значения (4). Отметим, однако, что наряду с уменьшением времени удержания блокировки за счет того, что в каждый момент захвачен только один мьютекс (и, стало быть, исключена возможность взаимоблокировки), мы немного изменили семантику операции по сравнению с реализацией, в которой оба мьютекса захватываются вместе. Если оператор в листинге 3.10 возвращает >true, то это означает лишь, что значение >lhs.some_detail в один момент времени равно значению >rhs.some_detail в другой момент времени. Между двумя операциями считывания значения могли измениться как угодно; например, между точками (2) и (3) программа могла обменять их местами, и тогда сравнение оказалось бы вообще бессмысленным. Таким образом, возврат оператором сравнения значения >true, означает, что значения были равны, пусть даже ни в какой момент времени фактическое равенство не наблюдалось. Очень важно следить, чтобы такие изменения семантики операций не приводили к проблемам: