Здравствуйте, uzhas, Вы писали:
U>опишу частный пример более подробно:
std::map<int, std::string> globalMap; // global var
//run in thread1
void t1_func()
{
globalMap[1] = "1";
globalMap[2] = "12";
globalMap[3] = "123";
spawnThread(&t2_func);
}
//run in thread2
void t2_func()
{
auto it = globalMap.find(2);
assert(it != globalMap.end());
}
U>интересует какие правила регулируют то, что все модификации, произведенные в первом потоке будут видны во втором потоке в момент вызова find. вернет ли find globalMap.end() при каких-то условиях и следует ли как-то явно подсказывать компилятору о необходимости сбросить все модификации перед чтением
Правила все те же — compiler reordering, hardware reordering.
Для данного кода необходима гарантия того, что публикация данных для других потоков происходит
строго после инициализации данных, причем гарантия как на уровне компилятора, так и на уровне CPU.
Первое обеспечивается вставкой компиляторного барьера (_ReadWriteBarrier на Visual C++)
между 'globalMap[3] = "123"' и вызовом spawnThread. Если этого не сделать, вызов spawnThread
может быть перемещен, например, в начало функции, до заполнения globalMap данными.
Конкретно для данного примера это выглядит маловероятным, но его легко упростить, сведя к
тому самому "клиническому случаю".
По поводу второго можно не беспокоиться, так как публикация данных для других потоков —
это так или иначе установка какого-нибудь флага, то есть, запись в память. В результате
получается комбинация store-store, которая эффектам hardware ordering не подвержена.
То есть, первый store (инициализация) будет гарантированно завершен до второго (публикация).
Чисто гипотетически, если spawnThread не использовала бы запись в память или барьер памяти,
это пришлось бы сделать за нее, опять же между 'globalMap[3] = "123"' и вызовом spawnThread,
только здесь барьер нужен не компиляторный, а хардварный (если быть точным, release-барьер).
Но такую ситуацию довольно трудно себе представить.
Еще можно заподозрить компилятор в том, что однажды он проявит излишнюю сообразительность и
реализует globalMap через регистры, из-за чего другие потоки не увидят данных.
Не поручусь за другие компиляторы, но Visual C++ не занимается такими вещами по отношению к
глобальным переменным, да и к локальным тоже, если есть хотя бы один шанс из миллиона, что ее
будут использовать за пределами области видимости.
Поэтому все, что нужно для данного примера — это вставить _ReadWriteBarrier перед
вызовом spawnThread в функции t1_func.
Все написанное справедливо для IA-32 и AMD64, на других процессорных архитектурах с
более "расслабленной" моделью памяти может потребоваться установка хардварного барьера.