Ложное совместное использование и переменные стека
-
29-09-2020 - |
Вопрос
У меня есть небольшие, но часто используемые функциональные объекты.Каждый поток получает свою копию.Все распределяется статически.Копии не передают никаких глобальных или статических данных.Нужно ли мне защищать эти объекты от ложного обмена?
Спасибо.РЕДАКТИРОВАТЬ:Вот игрушечная программа, использующая Boost.Threads.Может ли произойти ложное совместное использование поля? данные?
#include <boost/thread/thread.hpp>
struct Work {
void operator()() {
++data;
}
int data;
};
int main() {
boost::thread_group threads;
for (int i = 0; i < 10; ++i)
threads.create_thread(Work());
threads.join_all();
}
Решение
Ложный обмен между потоками - это когда 2 или более потоков используют одну и ту же строку кэша.
Например.:
struct Work {
Work( int& d) : data( d ) {}
void operator()() {
++data;
}
int& data;
};
int main() {
int false_sharing[10] = { 0 };
boost::thread_group threads;
for (int i = 0; i < 10; ++i)
threads.create_thread(Work(false_sharing[i]));
threads.join_all();
int no_false_sharing[10 * CACHELINE_SIZE_INTS] = { 0 };
for (int i = 0; i < 10; ++i)
threads.create_thread(Work(no_false_sharing[i * CACHELINE_SIZE_INTS]));
threads.join_all();
}
Потоки в первом блоке действительно страдают от ложного совместного использования.Потоки во втором блоке этого не делают (благодаря CACHELINE_SIZE
).
Данные в стеке всегда находятся "далеко" от других потоков.(Например.под windows, по крайней мере, пару страниц).
С вашим определением объекта функции может появиться ложное совместное использование, поскольку экземпляры Work
создается в куче, и это пространство кучи используется внутри потока.
Это может привести к нескольким Work
экземпляры должны быть смежными, и поэтому может потребоваться совместное использование строк кэша.
Но ...ваш образец не имеет смысла, потому что данные никогда не затрагиваются извне, и поэтому ложный обмен вызывается без необходимости.
Самый простой способ предотвратить подобные проблемы - скопировать ваши "общие" данные локально в стек, а затем поработать над копией стека.Когда ваша работа будет закончена, скопируйте ее обратно в выходной файл var.
Например,:
struct Work {
Work( int& d) : data( d ) {}
void operator()()
{
int tmp = data;
for( int i = 0; i < lengthy_op; ++i )
++tmp;
data = tmp;
}
int& data;
};
Это предотвращает все проблемы с общим доступом.
Другие советы
Я сделал справедливый билет исследования, и кажется, нет серебряного раствора пули для ложного обмена.Вот что я придумал (благодаря Кристоферу): 1) прокладка ваших данных с обеих сторон с неиспользованными или менее часто используемыми. 2) Скопируйте свои данные в стек и скопируйте его назад после завершения всей жесткой работы. 3) Используйте кэш выравниваемым распределением памяти.
Я не чувствую себя в полной безопасности, рассказывая подробности, но вот мое мнение:
(1) Ваш упрощенный пример не работает, поскольку повышение create_thread
ожидает ссылку, вы передаете временную.
(2) если бы вы использовали vector<Work>
с одним элементом из каждого потока или, в противном случае, хранить их в памяти последовательно, произойдет ложное совместное использование.