Почему мы сохраняем мьютекс вместо того, чтобы каждый раз объявлять его перед охраной?
Пожалуйста, рассмотрите этот классический подход, я упростил его, чтобы выделить точный вопрос:
#include <iostream>
#include <mutex>
using namespace std;
class Test
{
public:
void modify()
{
std::lock_guard<std::mutex> guard(m_);
// modify data
}
private:
/// some private data
std::mutex m_;
};
Это классический подход использования std::mutex
, чтобы избежать гонки данных.
Вопрос в том, почему мы держим дополнительный std::mutex
в нашем классе? Почему мы не можем объявлять его каждый раз перед объявлением std::lock_guard
подобным образом?
void modify()
{
std::mutex m_;
std::lock_guard<std::mutex> guard(m_);
// modify data
}
5 ответов:
Предположим, что два потока вызывают
modify
параллельно. Таким образом, каждый поток получает свой собственный, новый мьютекс. Таким образом,guard
не имеет никакого эффекта, поскольку каждый охранник блокирует другой мьютекс. Ресурс, который вы пытаетесь защитить от расовых условий, будет раскрыт.
Непонимание исходит из того, что такое
mutex
и для чего хорошlock_guard
.Мьютекс-это объект, который совместно используется различными потоками, и каждый поток может блокировать и освобождать мьютекс. Вот как работает синхронизация между различными потоками. Таким образом, вы можете работать с
m_.lock()
иm_.unlock()
также, но вы должны быть очень осторожны, что все пути кода (включая исключительные выходы) в вашей функции фактически разблокирует мьютекс.Чтобы избежать ловушки отсутствующих разблокировок, a
Локальный мьютекс не имеет смысла, поскольку он будет локальным, а не общим ресурсом. Локальный lock_guard отлично подходит, так как длительность хранения autmoatic предотвращает пропущенные блокировки / разблокировки. Надеюсь, это поможет.lock_guard
- объект-оболочка, который блокирует мьютекс при создании объекта-оболочки и разблокирует его при уничтожении объекта-оболочки. Поскольку объект-оболочка является объектом с автоматическим сроком хранения, вы никогда не пропустите разблокировку - вот почему.
Все это зависит от контекста того, что вы хотите предотвратить от параллельного выполнения.
Мьютекс будет работать, когданесколько потоков пытаются получить доступ к одному и тому же объекту мьютекса . Поэтому, когда 2 потока пытаются получить доступ и получить блокировку объекта мьютекса, только один из них будет успешным.
Теперь во втором примере, если два потока вызывают
Итак, чтобы ответить на ваш вопрос: это зависит от контекста. Миссия проекта состоит в том, чтобы гарантировать, что все потоки, которые не должны выполняться параллельно, будут поражать один и тот же объект мьютекса в критической части.modify()
, каждый поток будет иметь свой собственный экземпляр этого мьютекса , поэтому ничто не помешает им запустить эту функцию в параллель, как будто нет никакого мьютекса.
Синхронизация потоков включает в себя проверку наличия другого потока, выполняющего критическую секцию. А
mutex
это объекты, которые держат состояние для нас, чтобы проверить, было ли оно "заблокировано" потоком.lock_guard
с другой стороны, это обертка, котораяlock
s themutex
при инициализации иunlock
s это во время разрушения.Поняв это, должно быть яснее, почему должен быть только один экземпляр
mutex
, к которому всеlock_guard
нуждаются в доступе - они должны проверить, можно ли войти в критическую секцию против того же объекта. Во втором фрагменте вашего вопроса каждый вызов функции создает отдельныйmutex
, который виден и доступен только в локальном контексте.
Вам нужен мьютекс на уровне класса. В противном случае каждый поток имеет мьютекс для себя, и поэтому мьютекс не имеет никакого эффекта.
Если по какой-то причине вы не хотите, чтобы ваш мьютекс хранился в атрибуте класса, вы можете использовать статический мьютекс, как показано ниже.
Обратите внимание, что здесь имеется только 1 мьютекс для всех экземпляров класса. Если мьютекс хранится в атрибуте, у вас будет один мьютекс на экземпляр класса. В зависимости от ваших потребностей, вы можете предпочесть одно решение или другой.void modify() { static std::mutex myMutex; std::lock_guard<std::mutex> guard(myMutex); // modify data }