2015-11-27 3 views
5

мне нужно очень быстро (в смысле «низкой стоимости для читателя», а не «низкая задержка») механизм уведомлений об изменениях между потоками для обновления кэша чтения:Нужен ли мне барьер памяти для флага уведомления об изменении между потоками?

Ситуация

тему W (Writer) обновляет структуру данных (S) (в моем случае настройку на карте) только один раз в то время.

Тема R (Reader) хранит кэш S и читает это очень часто. Когда Thread W Обновления S Тема сообщения R необходимо уведомить об этом в разумные сроки (10-100 мс).

Архитектура ARM, x86 и x86_64. Мне нужно поддерживать C++03 с gcc 4.6 и выше.

Код

что-то вроде этого:

// variables shared between threads 
bool updateAvailable; 
SomeMutex dataMutex; 
std::string myData; 

// variables used only in Thread R 
std::string myDataCache; 

// Thread W 
SomeMutex.Lock(); 
myData = "newData"; 
updateAvailable = true; 
SomeMutex.Unlock(); 

// Thread R 

if(updateAvailable) 
{ 
    SomeMutex.Lock(); 
    myDataCache = myData; 
    updateAvailable = false; 
    SomeMutex.Unlock(); 
} 

doSomethingWith(myDataCache); 

Мой вопрос

В Thread R не запирающего или барьеры возникают в "быстром пути" (нет доступных обновлений) , Это ошибка? Каковы последствия этого проекта?

Нужно ли квалифицировать updateAvailable как volatile?

Will R Получить обновление В конечном итоге?

Мое понимание до сих пор

Безопасно в отношении целостности данных?

Это немного напоминает «Double Checked Locking». Согласно http://www.cs.umd.edu/~pugh/java/memoryModel/DoubleCheckedLocking.html, для исправления на C++ может использоваться барьер памяти.

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

Будет ли R получить обновление?

Вот где это становится сложно. Как я понимаю, процессор, работающий с потоком R, может кэшировать updateAvailable неопределенно, эффективно перемещая путь чтения до фактического if.

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

+0

PS: Я знаю о http://stackoverflow.com/questions/8517969/is-this-the-correct-way-to-atomically-read-and-write-a-bool –

+0

Код имеет неопределенное поведение, период. Добавление 'volatile' не меняет этого. –

+0

Можете ли вы уточнить? Единственный способ увидеть, где он неопределен, - это то, что ЦП не надежно распространяет хранилище на другие ядра. 'volatile' должен гарантировать, что компилятор фактически выдает хранилище на адрес памяти (независимо от того, что ЦПУ решает сделать с этим - это еще одна вещь ... - он может быть сохранен в очереди на запись, в кеше или что-то еще до достижения основной памяти) –

ответ

1

Нужно ли квалифицировать updateAvailable как volatile?

Как летучего не коррелирует с заправочной моделью в C++, вы должны использовать Atomics для сделать вашу программу строго стандартной confirmant:

На C++11 или более поздней версии предпочтительнее способа заключается в использовании atomic<bool> с memory_order_relaxed магазин/нагрузка:

atomic<bool> updateAvailable; 

//Writer 
.... 
updateAvailable.store(true, std::memory_order_relaxed); //set (under mutex locked) 

// Reader 

if(updateAvailable.load(std::memory_order_relaxed)) // check 
{ 
    ... 
    updateAvailable.store(false, std::memory_order_relaxed); // clear (under mutex locked) 
    .... 
} 

GCC 4.7, поскольку поддерживает подобную функциональность с в atomic builtins.

Что касается gcc 4.6, кажется, что нет строго подтверждающего способа уклонения от ограждений при доступе переменной updateAvailable. На самом деле забор памяти обычно намного быстрее, чем 10-100 м. Таким образом, вы можете использовать свой собственный atomic builtins:

int updateAvailable = 0; 

//Writer 
... 
__sync_fetch_and_or(&updateAvailable, 1); // set to non-zero 
.... 

//Reader 
if(__sync_fetch_and_and(&updateAvailable, 1)) // check, but never change 
{ 
    ... 
    __sync_fetch_and_and(&updateAvailable, 0); // clear 
    ... 
} 

ли это безопасно в отношении целостности данных?

Да, это безопасно. Ваша причина здесь абсолютно верна:

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


Это НЕ перепроверить замок!

Это прямо указано в самом вопросе.

В случае, когда updateAvailable является ложным, Читатель потока использует переменную myDataCache которая локального к нити (никаких других потоков не использовать). При двойной схеме блокировки все потоки используют общий объект напрямую.

Почему заборы памяти/барьеры НЕ ТРЕБУЕТСЯ здесь

Единственная переменная, доступ одновременно, является updateAvailable. myData имеет доступ с защитой от взаимного доступа, которая обеспечивает все необходимые ограждения. myDataCache - местный - для чтения.

Когда читатель видит нить updateAvailable переменную быть ложной, он использует myDataCache переменную, которая изменяется по резьбе. Заявка на программу гарантирует правильную видимость изменений в этом случае.

Что касается гарантий видимости для переменных updateAvailable, то стандарт C++ 11 предоставляет такие гарантии для атомной переменной, даже без заборов. 29.3 p13 говорит:

Реализации должны сделать атомные хранилища видимыми для атомных нагрузок в течение разумного промежутка времени.

Джонатан Wakely подтвердил, что этот пункт применяется даже к memory_order_relaxed доступ in chat.

+1

Летучие не будут работать здесь. OP нуждается в истинном атомизме. – SergeyA

+0

@SergeyA: Atomic требуется только для evade ** Неопределенное поведение ** согласно C++ 11 ** standard **. Но все часто используемые компиляторы (в том числе 'gcc') генерируют правильный код даже без него. Кроме того, апеллятор явно говорит о C++ 03, в котором отсутствует атомистика. – Tsyvarev

+3

Прежде всего, волатильность не будет работать таким образом на ARM. Период. Он работает на Intel из-за строгих гарантий заказа, которые не предусмотрены в ARM. Во-вторых, атомы существуют еще вне C++ - см. Мой ответ. – SergeyA

2

Используйте атомы С ++ и сделайте updateAvailable a std::atomic<bool>.Причина этого заключается в том, что не только процессор может видеть старую версию переменной, но особенно компилятор, который не видит побочный эффект другого потока и, таким образом, никогда не утруждает себя повторной настройкой переменной, поэтому вы никогда не увидите обновленное значение в потоке. Кроме того, таким образом вы получаете гарантированное атомное чтение, которого у вас нет, если вы просто прочитали значение.

Кроме этого, вы могли бы потенциально избавиться от блокировки, если, например, производитель только когда-либо производит данные, когда updateAvailable является ложным, вы можете избавиться от мьютекса, поскольку std::atomic<> обеспечивает соблюдение надлежащего упорядочения операций чтения и записи. Если это не так, вам все равно понадобится блокировка.

+0

std :: atomic недоступен в C++ 03. избавление от блокировки не требуется, так как обновления очень редко –

+1

@HannoS. то вам необходимо использовать встроенные модули вашего компилятора/платформы для обеспечения атомных операций. Volatile полезен только для использования обработчика сигналов. – JustSid

2

Здесь вам необходимо использовать забор для памяти. Без забора нет никаких гарантийных обновлений, будет когда-либо видел на другой теме. В C++ 03 вы можете использовать либо специфичный для платформы ASM-код (mfence на Intel, не знаю об ARM), либо использовать функции атомарного набора/get, предоставляемые операционной системой.

+0

Стандарт C++ требует ** garantee **, что указание в любом месте будет видно другим потоком в ** разумное количество времени **. Если вам нужна точная формулировка, я найду ее в стандарте. – Tsyvarev

+1

@ Цыварев Nope. Вы очень смущены. Эти гаранты применимы только при использовании атоматики. – SergeyA

+0

29.3.13: «Реализации должны сделать атомные хранилища видимыми для атомных нагрузок в течение разумного промежутка времени». Да, речь идет не о доступе к * любой * переменной, но * атомный магазин * включает '.store' с порядком' memory_order_relaxed', который без заборов. Что касается ограждений в данном примере, они разделены операциями .lock() и .unlock(). – Tsyvarev