Еще один способ справиться с гонками — рассматривать изменения структуры данных как
Самый простой механизм защиты разделяемых данных из описанных в стандарте С++ — это
3.2. Защита разделяемых данных с помощью мьютексов
Итак, у нас есть разделяемая структура данных, например связанный список из предыдущего раздела, и мы хотим защитить его от гонки и нарушения инвариантов, к которым она приводит. Как было бы здорово, если бы мы могли пометить участки кода, в которых производятся обращения к этой структуре данных,
Что ж, это вовсе не сказка — именно такое поведение вы получаете при использовании примитива синхронизации, который называется
Мьютексы — наиболее общий механизм защиты данных в С++, но панацеей они не являются; важно структурировать код так, чтобы защитить нужные данные (см. раздел 3.2.2), и избегать состояний гонки, внутренне присущих интерфейсам (см раздел 3.2.3). С мьютексами связаны и собственные проблемы, а именно:
3.2.1. Использование мьютексов в С++
В С++ для создания мьютекса следует сконструировать объект типа std::mutex, для захвата мьютекса служит функция-член lock(), а для освобождения — функция-член unlock(). Однако вызывать эти функции напрямую не рекомендуется, потому что в этом случае необходимо помнить о вызове unlock() на каждом пути выхода из функции, в том числе и вследствие исключений. Вместо этого в стандартной библиотеке имеется шаблон класса std::lock_guard, который реализует идиому RAII — захватывает мьютекс в конструкторе и освобождает в деструкторе, — гарантируя тем самым, что захваченный мьютекс обязательно будет освобожден. В листинге 3.1 показано, как с помощью классов std::mutex и std::lock_guard защитить список, к которому могут обращаться несколько потоков. Оба класса определены в заголовке .
Листинг 3.1. Защита списка с помощью мьютекса
#include
#include
#include
std::list(1)
std::mutex some_mutex; ←(2)
void add_to_list(int new_value) {
std::lock_guard(3)
some_list.push_back(new_value);
}
bool list_contains(int value_to_find) {
std::lock_guard(4)
return
std::find(some_list.begin(), some_list.end(), value_to_find) !=
some_list.end();
}
В листинге 3.1 есть глобальный список (1), который защищен глобальным же объектом std::mutex (2). Вызов std::lock_guard в add_to_list() (3) и list_contains() (4) означает, что доступ к списку из этих двух функций является взаимно исключающим: list_contains() никогда не увидит промежуточного результата модификации списка, выполняемой в add_to_list().