Включение std :: lock_guard в дополнительную область - PullRequest
0 голосов
/ 28 января 2019

Имеет ли смысл сделать что-то вроде добавления std::lock_guard в дополнительную область, чтобы период блокировки был как можно короче?

Псевдокод:

// all used variables beside the lock_guard are created and initialized somewhere else
...// do something

{ // open new scope
    std::lock_guard<std::mutex> lock(mut);
    shared_var = newValue;  
} // close the scope

... // do some other stuff (that might take longer)

Есть ли еще преимущества, кроме кратковременной блокировки?

Какие могут быть отрицательные побочные эффекты?

Ответы [ 5 ]

0 голосов
/ 29 января 2019

Не вижу смысла это делать.Если вы делаете что-то такое простое, как «установить одну переменную» - используйте atomic <> и вам вообще не нужны мьютекс и блокировка.Если вы делаете что-то сложное - извлеките этот код в новую функцию и используйте блокировку в первой строке.

0 голосов
/ 29 января 2019

Использование дополнительной области, специально предназначенной для ограничения времени жизни объекта std :: lock_guard, действительно является хорошей практикой.Как указывают другие ответы, блокировка вашего мьютекса на кратчайший период времени уменьшит вероятность того, что другой поток заблокирует мьютекс.

Я вижу еще один момент, который не был упомянут в других ответах: транзакционныйоперации.Давайте использовать классический пример перевода денег между двумя банковскими счетами.Чтобы ваша банковская программа была правильной, изменение баланса двух банковских счетов должно быть выполнено без разблокировки мьютекса между .В противном случае другой поток мог бы заблокировать мьютекс, пока программа находится в странном состоянии, когда только одна из учетных записей была зачислена / списана, а баланс другой учетной записи остался нетронутым!

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

РЕДАКТИРОВАТЬ:

Если по какой-либо причинесохранение мьютекса заблокированным на протяжении всей транзакции недопустимо, вы можете использовать следующий алгоритм:
1. Блокировка мьютекса, чтение входных данных, разблокировка мьютекса.
2. Выполнение всех необходимых вычислений, сохранение результатов локально.
3. Блокировка мьютекса, проверка того, что входные данные не изменились , выполнение транзакции с легкодоступными результатами, разблокировка мьютекса.

Если входные данные изменились во время выполненияна шаге 2 отбросьте результаты и начните заново со свежих входных данных.

0 голосов
/ 28 января 2019

Да, это имеет смысл.

Других преимуществ нет, побочных эффектов нет (это хороший способ написать это).

Еще лучше,состоит в том, чтобы извлечь ее в приватную функцию-член (если у вас есть операция, которая синхронизируется таким образом, вы могли бы также дать операции ее собственное имя):

{
    // all used variables beside the lock_guard are created and initialized somewhere else
    ...// do something

    set_var(new_value);

    ... // do some other stuff (that might take longer)
}

void your_class::set_value(int new_value)
{
    std::lock_guard<std::mutex> lock(mut);
    shared_var = new_value;
}
0 голосов
/ 28 января 2019

Там может быть недостаток: вы не можете защитить инициализации таким образом.Например:

{
    std::lock_guard<std::mutex> lock(mut);
    Some_resource var{shared_var};
} // oops! var is lost

вы должны использовать присвоение следующим образом:

Some_resource var;
{
    std::lock_guard<std::mutex> lock(mut);
    var = shared_Var;
}

, которое может быть неоптимальным для некоторых типов, для которого инициализация по умолчанию с последующим назначением менее эффективна, чем прямая инициализация,Более того, в некоторых ситуациях вы не можете изменить переменную после инициализации.(например, const переменные)


user32434999 указал на это решение:

// use an immediately-invoked temporary lambda
Some_resource var {
    [&] {
        std::lock_guard<std::mutex> lock(mut);
        return shared_var;
    } () // parentheses for invoke
};

Таким образом, вы можете защитить процесс поиска, но инициализацияСам по-прежнему не охраняется.

0 голосов
/ 28 января 2019

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

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

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

Возможно, есть еще один момент, который следует рассмотреть (у меня нет достаточного практического опыта, чтобы говорить с уверенностью).Блокировка / освобождение мьютекса потенциально может быть операцией с нетривиальными издержками производительности.Следовательно, может оказаться, что сохранение блокировки в течение немного более длительного периода вместо разблокировки и повторной блокировки несколько раз в течение одной операции может фактически улучшить общую производительность.Это то, что профилирование может показать вам.

...