Ложный общий доступ и переменные стека

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

Спасибо. РЕДАКТИРОВАТЬ: Вот игрушечная программа, которая использует 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();
}

3 ответа

Решение

Ложный обмен между потоками - это когда 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).

Данные в стеке всегда "далеко" от других потоков. (Например, под окнами, хотя бы пару страниц).

С вашим определением объекта функции может появиться ложное разделение, потому что экземпляры Work создать в куче, и это пространство кучи используется внутри потока.

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

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

Самый простой способ предотвратить подобные проблемы - это скопировать ваши "общие" данные локально в стек, а затем работать с копией стека. Когда ваша работа закончена, скопируйте ее обратно в выходную переменную.

Например:

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> с одним элементом для каждой нити или с другой стороны, если они последовательно находятся в памяти, произойдет ложное совместное использование.

Другие вопросы по тегам