View in Telegram
Еще один плюс RAII #опытным Основная мотивации использования raii - вам не нужно думать об освобождении ресурсов. Мол ручное управление ресурсами небезопасно, так как можно забыть освободить их и вообще не всегда понятно, когда это нужно делать. Но не все всегда зависит от вашего понимания программы. Вы можете в правильных местах расставить все нужные освобождения, но код будет все равно небезопасен. В чем проблема? В исключениях. Это такие противные малявки, которые прерывают нормальное выполнение программы в исключительных ситуациях. Так вот вы рассчитываете на "нормальное выполнение программы" и, исходя из этого, расставляете освобождения. А тут бац! И программа просто не доходит до нужной строчки.
std::shared_ptr<Value> DB::SelectWithCache(const Key& key) {
    mtx_.lock();
    if (auto it = cache.find(key); it != cache_.end()) {
        mtx_.unlock();
        return it->second;
    } else {
        std::shared_ptr<Value> result = Select(key);
        cache.insert({key, result});
        mtx_.unlock();
        return result;
    }
}
Простой код запроса к базе с кэшом. Что будет в том случае, если метод Select бросит исключение? unlock никогда не вызовется и мьютекс коннектора к базе будет навсегда залочен! Это очень печально, потому что ни один поток больше не сможет получить доступ к критической секции. Даже может произойти deadlock текущего потока, если он еще раз попытается захватить этот мьютекс. А это очень вероятно, потому что на запросы к базе скорее всего есть ретраи. Мы могли бы сделать обработку исключений и руками разлочить замок:
std::shared_ptr<Value> DB::SelectWithCache(const Key& key) {
    try {
        mtx_.lock();
        if (auto it = cache.find(key); it != cache_.end()) {
            mtx_.unlock();
            return it->second;
        } else {
            std::shared_ptr<Value> result = Select(key);
            cache.insert({key, result});
            mtx_.unlock();
            return result;
        }
    }
    catch (...) {
        Log("Caught an exception");
        mtx_.unlock();
    }
}
Однако самое обидное, что исключения, связанные с работой с базой, мы даже обработать не может внутри метода SelectWithCache. Это просто не его компетенция и код сверху некорректен с этой точки зрения. А снаружи метода объекта мы уже не сможем разблокировать мьютекс при обработке исключения, потому что это приватное поле. Выход один - использовать RAII.
std::shared_ptr<Value> DB::SelectWithCache(const Key& key) {
    std::lock_guard lg{mtx_};
    if (auto it = cache.find(key); it != cache_.end()) {
        return it->second;
    } else {
        std::shared_ptr<Value> result = Select(key);
        cache.insert({key, result});
        return result;
    }
}
При захвате исключения происходит раскрутка стека и вызов деструкторов локальных объектов. Это значит, что в любом случае вызовется деструктор lg и мьютекс освободится. Спасибо Михаилу за идею) Stay safe. Stay cool. #cpp11 #concurrency #cppcore #goodpractice
Love Center - Dating, Friends & Matches, NY, LA, Dubai, Global
Love Center - Dating, Friends & Matches, NY, LA, Dubai, Global
Find friends or serious relationships easily