2013-02-27 4 views
3

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

Definition

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 и, следовательно, он никогда не устанавливается, тем самым нарушая поток кода.

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

EDIT:

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

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; 
} 
+3

Можете ли вы показать определения 'LOCK()' и 'UNLOCK()'? –

+0

Также возвращаемое значение флага способ, который делается, подвержен логической ошибке. –

+0

@ Andy Prowl Я добавил определения для LOCK() и UNLOCK() – glo

ответ

2

этого вопрос у вас есть (который user1192878 ссылается) из-за задержки с компилятором нагрузки/магазины. Для реализации кода необходимо использовать memory barriers. Вы можете объявить volatile bool _flag;. Но это не нужно с compiler memory barriers для единой системы ЦП. Аппаратные барьеры (чуть ниже в ссылке Википедии) необходимы для решений с несколькими процессорами; аппаратный барьер гарантирует, что память/кеш локального процессора будут видны всем процессорам. В этом случае использование 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's concurrency pages также имеют некоторую информацию об альтернативных API, чтобы сделать то же самое. Я считаю, встроенный ассемблер gcc - это тот же синтаксис, что и для компиляторов Apple; но это может быть неправильно.

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

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

+0

Спасибо. Кажется, что это сработало и +1 для подробного объяснения. – glo

+0

Работает ли эта кросс-платформа? – ruben2020

+0

@ ruben2020: № «барьеры памяти» - это компилятор и/или ОС. Вот почему 'C++ 11' указал' std :: atomic', так что вы можете сделать это * кросс-платформенным способом. Если у вас этого нет, ссылка на wikipedia содержит ссылки на то, как это сделать на каждой платформе (создатели библиотеки 'C++ 11' делают это за вас). Также возможно, что дизайн процессора может сделать невозможным «аппаратные блокировки памяти»; но не для сотовых телефонов ** glo **. –

2

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

+0

Пробовал. Но не работает. Любые другие предложения? – glo

+0

-1 Мьютекс - проблема, а не решение. –

+0

@BillPringlemeir, если мьютекс - это ваша проблема, как это связано с моим ответом? – Slava

0

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

+0

Я мог бы попробовать это, но я хочу что-то, что я могу использовать на linux и mac слишком – glo

-1

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

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; 
} 
+0

-1 Зачем вам цикл в 'SetFlag()'? Это может привести к тому же тупику, который имеет плакат, если есть несколько авторов. Это является неотъемлемой частью API и не может быть исправлено. Представьте себе, что два процессора постоянно сражаются в 'SetFlag()' с разными значениями. Затем в 'GetFlag()', как долго мы хотим изменить переменную? Опять же, это ограничение в API разработчика/получателя без возможности исправления. –

+0

Я чувствую, что зло дало тебе -1. Ответ неправильный, но он показывает больше мысли, чем некоторые другие ответы. Мне нужно было немного подумать о вашем решении. –

+0

Я не думаю, что это имеет большое значение для логического, но если оно более сложное, например. две переменные - часы и минуты - которые должны быть согласованными, что и писатель, и читатель должны обеспечить согласованность. Хотя может показаться, что мы займемся циклом, я не думаю, что это вероятно для многопоточного одноядерного приложения, потому что только один будет работать одновременно и в конечном итоге победит. Но если он работает на нескольких ядрах одновременно, то, возможно, это проблема. Таким образом, лучше использовать RAII, атомные переменные или другие вещи. – ruben2020

0

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

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

Если вы хотите портативное осуществление блокировки, я предложил бы использовать OpenMP: How to use lock in openMP?

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

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

1

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

  1. Как другие отметили, RAII лучше, чем макрос.

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

  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 
    } 
    
    В вышеприведенном коде оптимизатор может делать неприятные вещи. Например, нет ничего, что помешало бы компилятору исключить первое назначение для доступа кFlag = true или его можно было бы переупорядочить, кэшировать. Например, для точки компилятора, если однопоточное, первое назначение для доступа кFlag бесполезно, потому что значение true никогда не используется.

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

    spinlock_lock(&lock); _flag = value; spinlock_unlock(&lock);

  5. Здесь также хорошо подходит и атомная переменная. Это может выглядеть следующим образом:
atomic_set(&accessing_flag, true);
+0

+1 для номера 5. –

Смежные вопросы