Я работаю над библиотекой времени выполнения, которая использует переключение контекста на уровне пользователя (используя Boost :: Context), и у меня возникают проблемы с использованием thread_level
переменные. Рассмотрим следующий (сокращенный) код:
thread_local int* volatile tli;
int main()
{
tli = new int(1); // part 1, done by thread 1
UserLevelContextSwitch();
int li = *tli; // part 2, done by thread 2
cout << li;
}
Так как есть два доступа к thread_local
переменная, основная функция преобразуется компилятором во что-то вроде этого (обратная от сборки):
register int** ptli = &tli; // cache address of thread_local variable
*ptli = new int(1);
UserLevelContextSwitch();
int li = **ptli;
cout << li;
Это похоже на правовую оптимизацию, так как Значение летучих tli
не кэшируется в реестре. Но адрес летучих tli
фактически кэшируется, а не читается из памяти в части 2.
И это проблема: после переключения контекста на уровне пользователя поток, который выполнил часть 1, уходит куда-то еще. Часть 2 затем берется другим потоком, который получает предыдущий стек и регистрирует состояние. Но теперь поток, который выполняет часть 2, читает значение tli
который принадлежит потоку 1.
Я пытаюсь найти способ предотвратить кеширование компилятором локальной переменной потока адрес, а также volatile
недостаточно глубоко Есть ли хитрость (желательно стандартная, возможно, специфичная для GCC) для предотвращения кеширования адресов локальных переменных потока?
Нет никакого способа соединить переключатели контекста уровня пользователя с TLS. Даже с атомарностью и полным ограничением памяти, адрес кэширования кажется законной оптимизацией, так как переменная thread_local является file-scope, статической переменной, которая не может быть перемещена, как предполагает компилятор. (хотя, возможно, некоторые компиляторы все еще могут быть чувствительными к таким барьерам памяти компилятора, как std::atomic_thread_fence
а также asm volatile ("" : : : "memory");
)
Cilk-плюс использования та же техника как вы описали, чтобы реализовать «кражу продолжения», когда другой поток может продолжить выполнение после точки синхронизации. И они явно препятствовать использование TLS в программе Cilk. Вместо этого они рекомендуют использовать «гиперобъекты» — особую функцию Cilk, которая заменяет TLS (а также обеспечивает семантику последовательного / детерминированного соединения). Смотрите также Cilk developer презентация около thread_local
и параллелизм.
Кроме того, Windows предоставляет FLS (Fiber Local Storage) в качестве замены TLS, когда Волокна (те же легкие переключатели контекста) используются.