Получить блокировку на двух мьютексах и избежать тупика - PullRequest
10 голосов
/ 12 февраля 2010

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

void foo::copy(const foo & rhs)
{
    pMutex->lock();
    rhs.pMutex->lock();
    // do copy
}

У Foo есть контейнер STL, а "do copy" по сути состоит из использования std :: copy. Как заблокировать оба мьютекса, не вводя взаимоблокировку?

Ответы [ 5 ]

15 голосов
/ 12 февраля 2010

Наложите какой-то общий порядок на экземпляры foo и всегда приобретайте их блокировки в порядке увеличения или уменьшения, , например, , foo1->lock(), а затем foo2->lock().

Другой подход состоит в том, чтобы использовать функциональную семантику и вместо этого написать метод foo::clone, который создает новый экземпляр, а не перекрывает существующий.

Если ваш код выполняет много блокировок, вам может понадобиться сложный алгоритм предотвращения тупиков, такой как алгоритм банкира .

1 голос
/ 12 февраля 2010

Как насчет этого?

void foo::copy(const foo & rhs)
{
    scopedLock lock(rhs.pMutex); // release mutex in destructor
    foo tmp(rhs);
    swap(tmp); // no throw swap locked internally
}

Это исключительная безопасность, а также довольно многопоточная. Чтобы сохранить поток на 100%, вам необходимо просмотреть весь путь к коду, а затем еще раз пересмотреть его другим взглядом, после чего еще раз просмотрите его ...

0 голосов
/ 17 февраля 2019

это известная проблема, уже есть стандартное решение. std::lock() можно вызывать одновременно на 2 или более мьютексах, избегая при этом тупиковых ситуаций. Больше информации здесь он предлагает рекомендации.

std :: scoped_lock предлагает оболочку RAII для этой функции и является как правило, предпочитает открытый вызов std :: lock.

конечно, это на самом деле не допускает ранних выпусков одного замка над другим, поэтому используйте std::defer_lock или std::adopt_lock, как я делал в этом ответе на аналогичный вопрос.

0 голосов
/ 22 декабря 2010

Вы можете попытаться заблокировать оба мьютекса одновременно, используя scoped_lock или auto_lock .... как банковский перевод сделать ...

void Transfer(Receiver recv, Sender send)
{
    scoped_lock rlock(recv.mutex);
    scoper_lock slock(send.mutex);

    //do transaction.
}
0 голосов
/ 12 февраля 2010

Во избежание тупика, вероятно, лучше всего подождать, пока оба ресурса не будут заблокированы:

Не знаю, какой API мьютекса вы используете, так что вот какой-то произвольный псевдокод, предположим, что can_lock() только проверяет, может ли он заблокировать мьютекс, и что try_lock() возвращает true, если он заблокирован, и false, если мьютекс уже заблокирован кем-то другим.

void foo::copy(const foo & rhs)
{
    for(;;)
    {
        if(! pMutex->cany_lock() || ! rhs.pMutex->cany_lock())
        {
            // Depending on your environment call or dont call sleep()
            continue;
        }
        if(! pMutex->try_lock())
            continue;
        if(! rhs.pMutex->try_lock())
        {
            pMutex->try_lock()
            continue;
        }
        break;
    }
    // do copy
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...