2008-09-22 3 views
27

У меня есть приложение, в котором работают 2 потока ... Есть ли какой-либо certanty, что когда я изменяю глобальную переменную из одного потока, другой заметит это изменение? У меня нет каких-либо Syncronization или систему взаимного исключения на месте ... но если это код работать все время (представьте себе глобальную BOOL имени dataUpdated):C++ Тема, общие данные

Тема 1:

while(1) { 
    if (dataUpdated) 
     updateScreen(); 
    doSomethingElse(); 
} 

тема 2:

while(1) { 
    if (doSomething()) 
     dataUpdated = TRUE; 
} 

компилятор как GCC оптимизировать ли этот код таким образом, чтобы он не проверяет глобальное значение, только с учетом его значение во время компиляции (потому что он не изменяется при одном и том же тире)?

PS: Как это для игрового приложения, это действительно неважно, будет ли прочитано, пока значение будет записано ... все, что имеет значение, - это то, что изменение замечено другим потоком.

ответ

23

Да. Нет. Может быть.

Во-первых, как уже отмечалось, вам необходимо сделать данныеUpdated volatile; в противном случае компилятор может свободно поднять его из цикла (в зависимости от того, может ли он видеть, что doSomethingElse не касается его).

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

Volatile considered harmful и Linux Kernel Memory Barriers - хороший фон по основным вопросам; Я не знаю ничего подобного, написанного специально для потокования. К счастью, нити не поднимают эти проблемы почти так же часто, как и аппаратные периферийные устройства, хотя описанный вами случай (флаг, указывающий завершение, с другими данными, считающимися действительными, если установлен флаг) - это именно то, matterns ...

2

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

volatile int myInteger; 
+2

Это работает только в модели памяти Java 1.5 +. Стандарт C++ не предназначен для потоковой передачи, а волатильность не гарантирует согласованность памяти между процессорами. Для этого вам нужен барьер памяти. – 2008-09-22 23:29:57

+0

Сам стандарт C++ в настоящее время не определяет эту семантику. Но все «последние» компиляторы почитают volatile как правильный намек. – Christopher 2008-09-22 23:38:21

+1

Нет, они этого не делают. volatile гарантирует, что компилятор выполнит нагрузку, как указано, и поэтому учитывает потоки, но не гарантирует, что базовая подсистема памяти сохранит причинность в истинном многопроцессорной системе. – puetzk 2008-09-22 23:56:17

2

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

3

Ваше решение будет использовать 100% процессор, среди прочих проблем. Google для «переменной условия».

3

Chris Jester-Янг отметил, что:

Это только работа под Java 1.5 модели + 's памяти. Стандарт C++ не предназначен для потоковой передачи, а волатильность не гарантирует согласованность памяти между процессорами. Вам нужен барьер памяти для этого

так, единственный верный ответ - это внедрение системы синхронизации, верно?

7

Вот пример, который использует переменное подталкивания условие:

bool _updated=false; 
boost::mutex _access; 
boost::condition _condition; 

bool updated() 
{ 
    return _updated; 
} 

void thread1() 
{ 
    boost::mutex::scoped_lock lock(_access); 
    while (true) 
    { 
    boost::xtime xt; 
    boost::xtime_get(&xt, boost::TIME_UTC); 
    // note that the second parameter to timed_wait is a predicate function that is called - not the address of a variable to check 
    if (_condition.timed_wait(lock, &updated, xt)) 
     updateScreen(); 
    doSomethingElse(); 
    } 
} 

void thread2() 
{ 
    while(true) 
    { 
    if (doSomething()) 
     _updated=true; 
    } 
} 
1

Если объем прав («Экстерн», глобальный, и т.д.), то изменение будет замечено. Вопрос в том, когда? И в каком порядке?

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

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

Выселение Pipeline Hazard на википедии или поиск Google для «инструкции компилятора переупорядочивания»

7

Используйте блокировку.Всегда всегда используйте блокировку для доступа к общим данным. Маркировка переменной как volatile не позволит компилятору оптимизировать чтение памяти, но не предотвратит другие проблемы, такие как memory re-ordering. Без блокировки нет гарантии, что запись в памяти doSomething() будет видна в функции updateScreen().

Единственным безопасным способом является использование memory fence, явно или неявно с использованием функции блокировки *, например.

1

Как уже говорилось, ключевое слово volatile - ваш друг. :-)

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

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

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

С учетом сказанного вы можете найти лучшие результаты (как указано в Jeff) с использованием семафора или переменной условия.

This - разумное введение в тему.

6

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

volatile int myInteger; 

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

Chris Jester-Young отметил, что в многопроцессорных системах может возникнуть проблема согласованности с таким изменением значения переменной.Это соображение, и это зависит от платформы.

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

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

Относительно когерентности это потенциальная проблема в многопроцессорной системе. Вопрос в том, реализует ли система полную когерентность кэша или нет между процессорами. Если это реализовано, это обычно делается с протоколом MESI на аппаратном уровне. В вопросе не были указаны платформы, но как платформы Intel x86, так и платформы PowerPC являются кешами, согласованными между процессорами для нормально отображаемых областей данных программы. Поэтому этот тип проблемы не должен быть проблемой для обычных обращений к памяти данных между потоками, даже если есть несколько процессоров.

Последний вопрос относительно атомарности, который возникает, специфичен для атомарности чтения-модификации-записи. То есть, как вы гарантируете, что если значение будет считаться обновленным по значению и написанным, это произойдет атомарно, даже для процессоров, если их больше одного. Таким образом, для этого, чтобы работать без конкретных объектов синхронизации, потребовалось бы, чтобы все потенциальные потоки, обращающиеся к переменной, были читателями ТОЛЬКО, но ожидали, что только один поток может когда-либо быть писателем за один раз. Если это не так, тогда вам нужен объект синхронизации, доступный для обеспечения атомарных действий при действиях чтения-изменения-записи в переменную.

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