Параллельное программирование на С++ в действии. Практика разработки многопоточных программ | страница 44
Листинг 3.2. Непреднамеренная передача наружу ссылки на защищённые данные
>class some_data {
> int а;
> std::string b;
>public:
> void do_something();
>};
>class data_wrapper {
>private:
> some_data data;
> std::mutex m;
>public :
> template
> void process_data(Function func)
(1) Передаем
> { │
"защищенные"
> std::lock_guard
данные поль-
> func(data); ←┘
зовательской
> }
функции
>};
>some_data* unprotected;
>void malicious_function(some_data& protected_data) {
> unprotected = &protected_data;
>}
>data_wrapper x;
>void foo
(2) Передаем
>{ │
вредоносную
> x.process_data(malicious_function); ←┘
функцию
> unprotected->do_something(); ←
(3) Доступ к "защищенным"
>}
данным в обход защиты
В этом примере функция-член >process_data
выглядит вполне безобидно, доступ к данным охраняется объектом >std::lock_guard
, однако наличие обращения к переданной пользователем функции >func
(1) означает, что >foo
может передать вредоносную функцию >malicious_function
, чтобы обойти защиту (2), а затем вызвать >do_something()
, не захватив предварительно мьютекс (3).
Здесь фундаментальная проблема заключается в том, что мы не сделали того, что собирались сделать: пометить все участки кода, в которых имеется доступ к структуре данных, как взаимно исключающие. В данном случае мы забыли о коде внутри >foo()
, который вызывает >unprotected->do_something()
. К сожалению, в этом стандартная библиотека С++ нам помочь не в силах: именно программист должен позаботиться о том, чтобы защитить данные мьютексом. Но не всё так мрачно — следование приведенной ниже рекомендации выручит в таких ситуациях. Не передавайте указатели и ссылки на защищенные данные за пределы области видимости блокировки никаким способом, будь то возврат из функции, сохранение в видимой извне памяти или передача в виде аргумента пользовательской функции.
Хотя описанная только что ситуация — самая распространенная ошибка при защите разделяемых данных, перечень подводных камней ей отнюдь не исчерпывается. В следующем разделе мы увидим, что гонка возможна даже, если данные защищены мьютексом.