C ++ Boost темы. Как использовать рекурсивную блокировку try? Тупик происходит в параллельном коде - PullRequest
2 голосов
/ 11 ноября 2011

Я разрабатываю потокобезопасный шаблон ленивых объектов в C ++, используя boost. Однако при этом я, если в моем приложении более одного потока, я попадаю в тупиковое состояние методом LazyObject calculate().

Это как-то связано с boost::recursive_mutex::scoped_try_lock, потому что, как только я экранировал код обычным мьютексом и просто позволил другим потокам ждать этого мьютекса, все прошло нормально. Однако недостаток простого блокирования других потоков состоит в том, что все они фактически должны проходить через много времени performCalculations(), потому что один поток очень часто меняет флаг calculated_ на false. Также обратите внимание, что performCalculations() является чисто виртуальным и производные экземпляры будут рекурсивно вызывать LazyObject::calculate(). Я хочу защитить эту бесконечную рекурсию мьютексом.

Вы видите, где я ошибаюсь?

My LazyObject имеет следующие атрибуты:

// protects resource frozen_ from simultaneous freeze, unfreeze calls
mutable boost::mutex frozenMutex_;
// protects resource calculated_ from simultaneous update, calculate calls
mutable boost::mutex calculatedMutex_;

// protects that only one thread can simultaneously call calculate
mutable boost::recursive_try_mutex waitMutex_;
// protects that only once can performCalculations be called (from same thread)
mutable boost::mutex blockingMutex_;

// mutex and semaphore for sleeping threads until calculate is ready
mutable boost::mutex condMutex_;
mutable boost::condition_variable condVariable_;

inline void LazyObject::calculate() const {

    boost::recursive_mutex::scoped_try_lock lock(waitMutex_);
    if (lock) {
        //recursive lock lets same thread pass, puts others on wait
        if (!calculated_ && !frozen_ && blockingMutex_.try_lock()) {
            // blockingMutex ensures that only once same thread 
            // can call performCalculations
            try {
                performCalculations();
                calculated_ = true;
                blockingMutex_.unlock();
                condVariable_.notify_all();
            } catch (...) {
                calculated_ = false;
                blockingMutex_.unlock();
                condVariable_.notify_all();
                throw;
            }   
        }
    } else {
        // start a non blocking wait until calculation is ready
        boost::mutex::scoped_lock lock(condMutex_);
        condVariable_.wait(lock);
    }
}

Ответы [ 3 ]

2 голосов
/ 11 ноября 2011

Функция, которую вы предоставили, выглядит звучащей.

Однако я сильно подозреваю, что у вас есть проблемы с порядком блокировки.У вас есть 5 мьютексов в одном классе.Вы должны гарантировать, что эти мьютексы всегда заблокированы в одном и том же порядке.В противном случае у вас будет мертвый замок.

Похоже, у вас очень сложный порядок блокировки:

  • 5 различных мьютексов
  • 1 рекурсивно
  • по крайней мере при попытке блокировки
0 голосов
/ 11 ноября 2011

Вы правы в том, что вышеуказанная функция не дает четкого понимания всей картины. Ниже в основном все взаимодействующие функции, которые стремятся к ресурсам * this

Мне удалось сократить количество мьютексов до использования только 3. Но я думаю, что проблему практически невозможно решить с меньшим количеством мьютексов. Обязательным условием является то, что метод обновления должен быть как можно более дешевым.

У меня все еще есть один вопрос, касающийся исключения. Как вы можете видеть, вычислительный поток, выполняющий executeCalculations, может выдать исключение. Если есть некоторые потоки, ожидающие сигнала для продолжения, они просто не могут продолжать работу, так как даже возникли исключения. Возможно ли использование boost, чтобы каким-то образом разрешить бодрствующим потокам генерировать то же исключение, которое было брошено в сигнальный поток Если да, можете ли вы предоставить явный код, как работает идея?

Моему классу нужны следующие аттрибуты.

// state variables indicating is calculation necessary
mutable bool calculated_, frozen_;

// flag that tells waking threads to throw exceptions if
// LazyObject::performCalculations() threw any exceptions 
mutable bool failed_;
// flag avoiding infinite recursion on single thread not recursively 
// calling LazyObject::performCalculations() through recursive calls 
// to LazyObject::calculate()
mutable bool calculating_;

// protects resources from simultaneous read & writes
mutable boost::mutex readWriteMutex_;

// protects that only one thread can simultaneously call calculate
//mutable boost::mutex waitMutex_;
mutable boost::recursive_try_mutex waitMutex_;

// mutex and semaphore for sleeping threads until calculate is ready
mutable boost::mutex condMutex_;
mutable boost::condition_variable condVariable_;


inline void LazyObject::performCalculations() {
    // let derived classes specialize own implementation
}

inline void LazyObject::update() {
    // observers don't expect notifications from frozen objects
    // LazyObject forwards notifications only once until it has been 
    // recalculated
    readWriteMutex_.lock();
    calculated_ = false;
    readWriteMutex_.unlock();
    if (!frozen_) {
        notifyObservers();
    }
}

inline void LazyObject::recalculate() {
    readWriteMutex_.lock();
    bool wasFrozen = frozen_;
    calculated_ = frozen_ = false;
    try {
        readWriteMutex_.unlock();
        calculate();
    } catch (...) {
        readWriteMutex_.lock();
        frozen_ = wasFrozen;
        readWriteMutex_.unlock();
        notifyObservers();
        throw;
    }
    readWriteMutex_.lock();
    frozen_ = wasFrozen;
    readWriteMutex_.unlock();
    notifyObservers();
}

inline void LazyObject::freeze() {
    readWriteMutex_.lock();
    frozen_ = true;
    readWriteMutex_.unlock();
}

inline void LazyObject::unfreeze() {
    readWriteMutex_.lock();
    frozen_ = false;
    readWriteMutex_.unlock();
    // send notification, just in case we lost any
    notifyObservers();
}

inline void LazyObject::calculate() const {

    //boost::recursive_mutex::scoped_try_lock lock(waitMutex_); 

    readWriteMutex_.lock();
    // see a snapshot of object's status
    if (!calculated_ && !frozen_) {
        if (waitMutex_.try_lock()) {
            //recursive lock lets same thread pass, puts others on wait
            if (calculating_) {
                readWriteMutex_.unlock();
                waitMutex_.unlock();
                return;
            } else {
                calculating_ = true;
            }
            readWriteMutex_.unlock();

            try {
                performCalculations();

                readWriteMutex_.lock();
                calculating_ = false;
                failed_ = false;
                calculated_ = true;
                readWriteMutex_.unlock();
                waitMutex_.unlock();
                condVariable_.notify_all();
                return;
            } catch (...) {
                readWriteMutex_.lock();
                calculating_ = false;
                failed_ = true;
                calculated_ = false;
                readWriteMutex_.unlock();
                waitMutex_.unlock();
                condVariable_.notify_all();
                throw;
            }   
        } else {
            // start a non blocking wait until calculation is ready
            readWriteMutex_.unlock();
            boost::mutex::scoped_lock lock(condMutex_);
            condVariable_.wait(lock);
            if (failed_)
                throw std::exception();
            else
                return;
        }
    }
    // no need to calculate
    readWriteMutex_.unlock();
}
0 голосов
/ 11 ноября 2011

Возможно, вы могли бы дать описание того, чего вы хотите достичь. Вы не предоставили весь код, так что можно только догадываться.

Например, вы можете сделать взаимоблокировку, если один поток только что установил calculated_ в true, выполнил condVariable_.notifyAll() и получает приоритет перед разблокировкой waitMutex_, а затем другой поток блокируется в condVariable_.wait(lock), а затем никто никогда не приходит в проснись.

Я вижу в комментарии, который вы написали "мьютекс и семафор", обратите внимание, что переменная условия не имеет памяти, она не похожа на семафор или объект события Windows.

Дайте лучшее описание проблемы, я не думаю, что этот код выше может быть спасен:)

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...