Блокировка на взаимном исключении также относятся к вызванным функциям?

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

Удостоверяющийся ориентированный на многопотоковое исполнение доступ к одиночному элементу тогда достигается обычным способом со взаимоисключающими разделами / критическими разделами.

Ленивая инициализация может также быть достигнута с помощью подобного механизма. Обычная проблема, с которой встречаются с этим, состоит в том, что взаимное исключение, необходимое для обеспечения потокобезопасности, часто инициализируется в самом одиночном элементе, который просто продвигает проблему потокобезопасности к инициализации взаимоисключающего раздела / критического раздела. Один способ преодолеть эту проблему состоит в том, чтобы создать и инициализировать взаимоисключающий раздел / критический раздел в основном потоке Вашего приложения тогда передают его одиночному элементу через вызов к статической функции членства. Тяжелая инициализация одиночного элемента может тогда произойти ориентированным на многопотоковое исполнение способом с помощью этого предварительно инициализированного взаимного исключения / критического раздела. Например:

// A critical section guard - create on the stack to provide 
// automatic locking/unlocking even in the face of uncaught exceptions
class Guard {
    private:
        LPCRITICAL_SECTION CriticalSection;

    public:
        Guard(LPCRITICAL_SECTION CS) : CriticalSection(CS) {
            EnterCriticalSection(CriticalSection);
        }

        ~Guard() {
            LeaveCriticalSection(CriticalSection);
        }
};

// A thread-safe singleton
class Singleton {
    private:
        static Singleton* Instance;
        static CRITICAL_SECTION InitLock;
        CRITICIAL_SECTION InstanceLock;

        Singleton() {
            // Time consuming initialization here ...

            InitializeCriticalSection(&InstanceLock);
        }

        ~Singleton() {
            DeleteCriticalSection(&InstanceLock);
        }

    public:
        // Not thread-safe - to be called from the main application thread
        static void Create() {
            InitializeCriticalSection(&InitLock);
            Instance = NULL;
        }

        // Not thread-safe - to be called from the main application thread
        static void Destroy() {
            delete Instance;
            DeleteCriticalSection(&InitLock);
        }

        // Thread-safe lazy initializer
        static Singleton* GetInstance() {
            Guard(&InitLock);

            if (Instance == NULL) {
                Instance = new Singleton;
            }

            return Instance;
        }

        // Thread-safe operation
        void doThreadSafeOperation() {
            Guard(&InstanceLock);

            // Perform thread-safe operation
        }
};

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

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

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

8
задан int3 2 August 2009 в 05:52
поделиться

3 ответа

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

Лучшей стратегией будет такая настройка:

class A {
  QMutex mutex;

  void f() {  
    QMutexLocker ml(mutex); // Acquire a lock on mutex
    g();

    // The lock on the mutex will be released when ml is destroyed.
    // This happens at the end of this function.
  }

  // ...
};

В этом случае мьютекс заблокирован, пока мл существует, поэтому также в то время, когда поток проводит внутри g () . Если другой поток вызовет f () в это время, он заблокирует создание своего объекта ml до тех пор, пока первый поток не выйдет из функции, и новый поток не сможет заблокировать мьютекс .

17
ответ дан 3 November 2019 в 14:19
поделиться

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

В вашем вопросе, у вас есть функция f, выделяющая экземпляр Mutex. Этого недостаточно, чтобы заблокировать его. Вы должны специально вызвать mutex.lock () (в Qt, но также и вообще, если вы не используете pthread, в этом случае используйте pthread_mutex_lock и получайте удовольствие от низкоуровневых, платформенно-зависимых вещей. Qt очень хорошо абстрагирует).

вот пример с Qt

  void MyClass::doStuff( int c )
    {
        mutex.lock();
        a = c;
        b = c * 2;
        mutex.unlock();
    } 

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

Если это единственный способ для ваших потоков достичь g (), то вы синхронизируете этот доступ.

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

Например (у меня нет Qt, поэтому я не могу скомпилировать этот код, и я прекратил кодировать с его помощью 2 года назад, поэтому он не мог работать)

class Foo {
public:
   void method(void) {
      mutex.lock();
      cout << "method called";
      // long computation
      mutex.unlock();
   }

private:
  QMutex mutex;
};

Хорошо, в этом случае предположим, что у вас есть два потока, 1 и 2, и два экземпляра класса Foo, a и b. Предположим, что поток 1 вызывает a.method (), а поток 2 вызывает b.method (). В этом случае два мьютекса являются разными экземплярами, поэтому каждый поток будет выполнять вызов независимо и работать параллельно.

Предположим, у вас есть два потока, 1 и 2, и один экземпляр класса Foo, который совместно используется две нити. если поток 1 вызывает a.method (), а затем поток 2 вызывает a.method (), поток 2 останавливается и ожидает, пока блокировка мьютекса не будет снята.

Наконец,

class Foo {
public:
   void method(void) {
      mutex.lock();
      cout << "method called";
      // long computation
      mutex.unlock();
   }

private:
  static QMutex mutex;
};

QMutex Foo::mutex;

В этом случае мьютекс является классом статическая переменная. У вас есть только один экземпляр мьютекса для каждого экземпляра объекта. Предположим, у вас была такая же ситуация, как в первом случае выше: два потока и два экземпляра. В этом случае, когда второй поток пытается вызвать b.method (), ему придется дождаться завершения a.method () первым потоком, поскольку блокировка теперь уникальна и используется всеми экземплярами вашего класса.

Для больше информации, Qt имеет хороший учебник по многопоточности

https://doc.qt.io/qt-5/threads.html

8
ответ дан 3 November 2019 в 14:19
поделиться

Ваш мьютекс размещен локально в стеке. Таким образом, вызов f () из одного потока блокирует его собственный экземпляр мьютекса. Любой другой вызов f () из другого потока заблокирует свой собственный. Таким образом, может возникнуть состояние гонки с данными, доступными из g ()! Даже если вы вызываете это в том же экземпляре класса:

MyClass foo;
(In thread 1) foo->f();
(In thread 2) foo->f();

Как лучше управлять блокировкой, зависит от того, что вы хотите сделать. В соответствии с тем, что вы сказали, я думаю, лучшей политикой было бы прямое изменение реализации g (): он должен заблокировать мьютекс, объявленный, например, как глобальный, или как статический в g (), чтобы его можно было использовать для любого вызова g (). Насколько я понимаю, вы хотите заблокировать свои данные глобально?

2
ответ дан 3 November 2019 в 14:19
поделиться
Другие вопросы по тегам:

Похожие вопросы: