Re[3]: multithreading : visibility control
От: okman Беларусь https://searchinform.ru/
Дата: 30.05.13 20:04
Оценка: 10 (1)
Здравствуйте, 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, на других процессорных архитектурах с
более "расслабленной" моделью памяти может потребоваться установка хардварного барьера.
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.