Почему мы сохраняем мьютекс вместо того, чтобы каждый раз объявлять его перед охраной?


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

#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 6

5 ответов:

Предположим, что два потока вызывают modify параллельно. Таким образом, каждый поток получает свой собственный, новый мьютекс. Таким образом, guard не имеет никакого эффекта, поскольку каждый охранник блокирует другой мьютекс. Ресурс, который вы пытаетесь защитить от расовых условий, будет раскрыт.

Непонимание исходит из того, что такое mutex и для чего хорош lock_guard.

Мьютекс-это объект, который совместно используется различными потоками, и каждый поток может блокировать и освобождать мьютекс. Вот как работает синхронизация между различными потоками. Таким образом, вы можете работать с m_.lock() и m_.unlock() также, но вы должны быть очень осторожны, что все пути кода (включая исключительные выходы) в вашей функции фактически разблокирует мьютекс.

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

Локальный мьютекс не имеет смысла, поскольку он будет локальным, а не общим ресурсом. Локальный lock_guard отлично подходит, так как длительность хранения autmoatic предотвращает пропущенные блокировки / разблокировки. Надеюсь, это поможет.

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

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

Теперь во втором примере, если два потока вызывают modify(), каждый поток будет иметь свой собственный экземпляр этого мьютекса , поэтому ничто не помешает им запустить эту функцию в параллель, как будто нет никакого мьютекса.

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

Синхронизация потоков включает в себя проверку наличия другого потока, выполняющего критическую секцию. А mutex это объекты, которые держат состояние для нас, чтобы проверить, было ли оно "заблокировано" потоком. lock_guard с другой стороны, это обертка, которая locks the mutex при инициализации и unlocks это во время разрушения.

Поняв это, должно быть яснее, почему должен быть только один экземпляр mutex , к которому все lock_guardнуждаются в доступе - они должны проверить, можно ли войти в критическую секцию против того же объекта. Во втором фрагменте вашего вопроса каждый вызов функции создает отдельный mutex, который виден и доступен только в локальном контексте.

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

Если по какой-то причине вы не хотите, чтобы ваш мьютекс хранился в атрибуте класса, вы можете использовать статический мьютекс, как показано ниже.

void modify()
{
    static std::mutex myMutex;
    std::lock_guard<std::mutex> guard(myMutex);
    // modify data
}
Обратите внимание, что здесь имеется только 1 мьютекс для всех экземпляров класса. Если мьютекс хранится в атрибуте, у вас будет один мьютекс на экземпляр класса. В зависимости от ваших потребностей, вы можете предпочесть одно решение или другой.