Этот вопрос - хрестоматийный пример того, что затрудняет параллельное программирование. Действительно подробное объяснение могло бы заполнить всю книгу, а также множество статей разного качества.
Но можно немного резюмировать. Глобальная переменная находится в области памяти, видимой для всех потоков. (Альтернативой является локальное хранилище потока, которое может видеть только один поток.) Итак можно ожидать, что если у вас есть глобальная переменная G, и поток A записывает в нее значение x, то поток B em> увидит x при чтении этой переменной позже. И в общем, правда - в конце концов. Интересно то, что происходит до «в конце концов».
Самым большим источником хитростей являются согласованность памяти и согласованность памяти.
Согласованность описывает, что происходит, когда поток A записывает в G, а поток B пытается прочитать его почти в один и тот же момент. Представьте, что поток A и B находится на разных процессорах (давайте также назовем их A и B для простоты). Когда A записывает в переменную, между ним и памятью, которую видит поток B, существует множество схем. Во-первых, A, вероятно, напишет на количество сигналов, которые должны идти вперед и назад по проводам и конденсаторов и транзисторов и сложный диалог между кешем и основным блоком памяти. Между тем, у B есть собственный кеш. Когда изменения происходят в основной памяти, B может не сразу их увидеть, по крайней мере, до тех пор, пока не заполнит свой кэш из этой строки. И так далее. В общем, может пройти много микросекунд, прежде чем изменение потока A станет видимым для B.
Согласованность описывает, что происходит, когда A записывает в переменную G, а затем в переменную H. Если он считывает эти переменные, он увидит, что записи происходят в этом порядке. Но поток B может видеть их в другом порядке, в зависимости от того, очищается ли сначала H из кеша обратно в основную оперативную память. И что произойдет, если и A, и B одновременно пишут в G (по настенным часам), а затем попытаются прочитать ответ из Это? Какое значение они увидят?
Согласованность и согласованность обеспечиваются на многих процессорах с помощью операций барьер памяти. Например, PowerPC имеет синхронизацию a > код операции, который гласит: «Гарантия того, что любая запись, сделанная любым потоком в основную память, будет видна при любом чтении после этой операции синхронизации». (в основном это делается путем перепроверки каждой строки кэша относительно основной ОЗУ.) Архитектура Intel делает это до некоторой степени автоматически, если вы заранее предупредите его, что« эта операция затрагивает синхронизированную память ».
Тогда у вас возникнет проблема переупорядочения компилятора. Вот где код
int foo( int *e, int *f, int *g, int *h)
{
*e = *g;
*f = *h;
// <-- another thread could theoretically write to g and h here
return *g + *h ;
}
может быть внутренне преобразован компилятором во что-то вроде
int bar( int *e, int *f, int *g, int *h)
{
int b = *h;
int a = *g;
*f = b ;
int result = a + b;
*e = a ;
return result;
}
что может дать вам совершенно другой результат, если другой поток выполнит запись в указанном выше месте! также обратите внимание на то, что записи выполняются в другом порядке в bar
. Это проблема, которую должен решить volatile - он не позволяет компилятору сохранять значение *g
в локальном, но вместо этого заставляет его перезагружать это значение из памяти каждый раз, когда он видит *g
.
Как видите, этого недостаточно для обеспечения согласованности и согласованности памяти на многих процессорах. Это действительно было изобретено для случаев, когда у вас был один процессор, который пытался читать с оборудования с отображением в память - например, последовательный порт, где вы хотите смотреть на место в памяти каждые n микросекунд, чтобы увидеть какое значение в настоящее время находится в сети. (Именно так I / O работал, когда они изобрели C.)
Что с этим делать? Как я уже сказал, на эту тему есть целые книги. Но краткий ответ заключается в том, что вы, вероятно, захотите использовать средства, которые ваша операционная система / платформа времени выполнения предоставляет для синхронизированной памяти.
Например, Windows предоставляет API доступа к заблокированной памяти, чтобы дать вам четкий способ обмена данными между потоками A и B. GCC пытается предоставить некоторые похожие функции. Строительные блоки потоковой передачи Intel предоставляют вам удобный интерфейс для платформ x86 / x64 и библиотека поддержки потоков C ++ 11 также предоставляет некоторые возможности.
person
Crashworks
schedule
03.09.2013
volatile
, чтобы увидеть немедленные обновления в разных потоках на указателе - person thumbmunkeys   schedule 03.09.2013volatile
. - person cHao   schedule 03.09.2013volatile
не дает гарантий непротиворечивости памяти. - person user7116   schedule 03.09.2013volatile
. - person cHao   schedule 03.09.2013volatile
ничего не гарантирует о видимости нового значения, не имеет значения. Дело в том, что он заставляет компилятор предполагать, что значение может измениться, поэтому что-то вродеwhile (g_ptr);
не повторяется навсегда, когда вы включаете оптимизацию. - person cHao   schedule 03.09.2013while(g_ptr)
, освободит мьютекс, чтобы иметь возможность его изменить. Барьер памяти, обеспечиваемый мьютексом, не позволяет компилятору переупорядочивать, делая volatile совершенно бесполезным. - person Étienne   schedule 03.09.2013mov esi, [g_ptr]
и использоватьesi
везде, где он иначе использовал бы[g_ptr]
. - person cHao   schedule 03.09.2013g_ptr
имеет правильные значения, поскольку этот компилятор может иметь значение, кэшированное в некоторых регистрах. - person bkausbk   schedule 03.09.2013while(g_ptr)
без использования какой-либо функции в цикле, значениеg_ptr
может быть кэшировано, если вы не объявите его изменчивым. Но если вы заблокируете мьютекс для чтения значенияg_ptr
, его значение не может быть кэшировано компилятором. - person Étienne   schedule 03.09.2013volatile
. Будь прокляты мьютексы и заборы памяти; они даже не рассматривались до C11. Что касается C11, я не могу предположить, что это изменилось, пока я не увидел, где это указано в спецификации. - person cHao   schedule 04.09.2013