мьютексный замок не отпирается


Я использую мьютекс для блокировки и разблокировки переменной, когда я вызываю getter из основного потока непрерывно в цикле обновления, и я вызываю setter из другого потока. Я предоставил код для сеттера и геттера ниже

Определение

bool _flag;
System::Mutex m_flag;

Звонки

#define LOCK(MUTEX_VAR) MUTEX_VAR.Lock();
#define UNLOCK(MUTEX_VAR) MUTEX_VAR.Unlock();

void LoadingScreen::SetFlag(bool value)
{
    LOCK(m_flag);
    _flag = value;
    UNLOCK(m_flag);
}

bool LoadingScreen::GetFlag()
{
    LOCK(m_flag);
    bool value = _flag;
    UNLOCK(m_flag);

    return value;
}

Это хорошо работает половину времени, но иногда переменная блокируется при вызове SetFlag, и поэтому она никогда не устанавливается, тем самым нарушая поток кода.

Может ли кто-нибудь сказать мне, как решить этот вопрос?

Правка:

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

bool _flag;
bool accessingFlag = false;

void LoadingScreen::SetFlag(bool value)
{
    if(!accessingFlag)
    {
        _flag = value;
    }
}

bool LoadingScreen::GetFlag()
{
    accessingFlag = true;
    bool value = _flag;
    accessingFlag = false;

    return value;
}
6 3

6 ответов:

Возникшая проблема (на которую ссылаетсяuser1192878 ) связана с задержкой загрузки/хранения компилятора. Для реализации кода необходимо использовать барьеры памяти . Вы можете объявить volatile bool _flag;. Но это не требуется с барьерами памяти компилятора для одной процессорной системы. Аппаратные барьеры (чуть ниже по ссылке Википедии) необходимы для мультипроцессорных решений; аппаратные барьеры гарантируют, что память/кэш локального процессора видны всем процессорам. Использование mutex и другие блокировки в этом случае не нужны. Чего именно они добиваются? Они просто создают тупики и не нужны.

bool _flag;
#define memory_barrier __asm__ __volatile__ ("" ::: "memory") /* GCC */

void LoadingScreen::SetFlag(bool value)
{
    _flag = value;
    memory_barrier(); /* Ensure write happens immediately, even for in-lines */
}

bool LoadingScreen::GetFlag()
{
   bool value = _flag;
   memory_barrier(); /* Ensure read happens immediately, even for in-lines */
   return value;
}

Мьютексы нужны только тогда, когда одновременно задается несколько значений. Вы также можете изменить тип bool на sig_atomic_t или LLVM atomics. Однако это довольно педантично, поскольку bool будет работать почти на каждой практической архитектуре процессора. страницы параллелизма Cocoa также содержат некоторую информацию об альтернативных API. чтобы сделать то же самое. Я считаю, что встроенный ассемблерgcc имеет тот же синтаксис, что и компиляторы Apple; но это может быть неверно.

У API есть некоторые ограничения. Экземпляр GetFlag() возвращает, что-то может вызвать SetFlag(). GetFlag() возвращаемое значение тогда является устаревшим. Если у вас есть несколько авторов, то вы можете легко пропустить одного SetFlag(). Это может быть важно, если логика более высокого уровня склонна к проблемамABA . Однако все эти проблемы существуют с / без мьютексов. Барьер памяти только решает проблему, что компилятор/процессор не будет кэшировать SetFlag() в течение длительного времени, и он будет перечитывать значение в GetFlag(). Объявление volatile bool flag обычно приводит к тому же поведению, но с дополнительными побочными эффектами и не решает проблем с несколькими процессорами.

std::atomic<bool>согласно Стефану и atomic_set(&accessing_flag, true); обычно будет делать то же самое, что описано выше в их реализациях. Вы можете использовать их, если они есть доступно на ваших платформах.

Прежде всего вы должны использовать RAII для блокировки/разблокировки мьютекса. Во-вторых, вы либо не показываете какой-то другой код, который использует _flag напрямую, либо что-то не так с мьютексом, который вы используете (маловероятно). Какую библиотеку предоставляет System:: Mutex?

Код выглядит правильно, если правильно реализован мьютекс System::. Кое-что следует упомянуть:

  1. Как указывали другие, RAII лучше, чем macro.

  2. Возможно, было бы лучше определить accessingFlag и _flag как volatile.

  3. Я думаю, что временное решение, которое вы получили, не является правильным, если вы компилируете с оптимизацией.

    bool LoadingScreen::GetFlag()
    {
      accessingFlag = true;  // might be reordered or deleted
      bool value = _flag;  // might be optimized away
      accessingFlag = false;    // might be reordered before value set
      return value;   // might be optimized to directly returen _flag or register
    }
    
    в приведенном выше коде оптимизатор может делать неприятные вещи. Например, ничто не мешает компилятору устранить первый присваивание accessingFlag=true, или оно может быть переупорядочено, кэшировано. Например, с точки зрения компилятора, если он однопоточный, первое назначение accessingFlag бесполезно, поскольку значение true никогда не используется.
  4. Использование мьютекса для защиты одной переменной bool дорого, так как большая часть времени тратится на переключение режима ОС (от ядра к пользователю и обратно). Возможно, неплохо использовать spinlock (детализация кода зависит от целевой платформы). Это должно быть что-то например:

    spinlock_lock(&lock); _flag = value; spinlock_unlock(&lock);
  5. здесь также хороша атомарная переменная. Это может выглядеть так:
atomic_set(&accessing_flag, true);

Вы рассматривали возможность использования CRITICAL_SECTION? Это доступно только в Windows, поэтому вы теряете некоторую переносимость,но это эффективный пользовательский мьютекс.

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

  1. блокировка m_flags правильно инициализирована и не модифицирована никаким другим кодом.
  2. реализация блокировки правильна.

Если вам нужна портативная реализация блокировки, я бы предложил использовать OpenMP: Как использовать lock in в OpenMP?

Из вашего описания кажется, что вы хотите быть заняты ожиданием потока для обработки некоторых входных данных. В этом случае решение Стефана (объявить флаг std:: atomic), вероятно, лучше всего. В полусумасшедших системах x86 можно также объявить флаг volatile int. Только не делайте этого для не выровненных полей (упакованных структур).

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

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

bool _flag;

void LoadingScreen::SetFlag(bool value)
{
    do
    {
       _flag = value;
    } while (_flag != value);
}

bool LoadingScreen::GetFlag()
{
    bool value;

    do
    {
        value = _flag;
    } while (value != _flag);

    return value;
}