У меня есть несколько многопоточных кода C ++ со следующей структурой:
do_thread_specific_work();
update_shared_variables();
//checkpoint A
do_thread_specific_work_not_modifying_shared_variables();
//checkpoint B
do_thread_specific_work_requiring_all_threads_have_updated_shared_variables();
То, что следует за контрольной точкой B, - это работа, которая могла бы начаться, если бы все потоки достигли только контрольной точки A, отсюда мое понятие «мягкого барьера».
Как правило, многопоточные библиотеки предоставляют только «жесткие барьеры», в которых все потоки должны достигнуть некоторой точки, прежде чем любая из них сможет продолжить работу. Очевидно, что на контрольно-пропускном пункте В может быть использован жесткий барьер.
Использование мягкого барьера может привести к сокращению времени выполнения, тем более что работа между контрольными точками A и B может быть не сбалансирована нагрузкой между потоками (т. Е. 1 медленный поток, достигший контрольной точки A, но не B, может вызывать все другие подождать у барьера непосредственно перед контрольно-пропускным пунктом B).
Я пытался использовать атомику для синхронизации вещей, и я знаю со 100% уверенностью, что это НЕ гарантированно работает. Например, используя синтаксис openmp, перед началом параллельного раздела:
shared_thread_counter = num_threads; //known at compile time
#pragma omp flush
Затем на контрольной точке A:
#pragma omp atomic
shared_thread_counter--;
Затем в контрольной точке B (с использованием опроса):
#pragma omp flush
while (shared_thread_counter > 0) {
usleep(1); //can be removed, but better to limit memory bandwidth
#pragma omp flush
}
Я разработал несколько экспериментов, в которых я использую атом, чтобы указать, что какая-то операция до ее завершения. Эксперимент будет работать с двумя потоками в большинстве случаев, но постоянно терпит неудачу, когда у меня много потоков (например, 20 или 30). Я подозреваю, что это из-за структуры кэширования современных процессоров. Даже если один поток обновляет какое-то другое значение перед выполнением атомарного декремента, другой поток в этом порядке не гарантируется. Рассмотрим случай, когда другое значение является пропуском кеша, а атомарный декремент - попаданием в кеш.
Итак, вернемся к моему вопросу, как ПРАВИЛЬНО реализовать этот «мягкий барьер»? Есть ли встроенная функция, которая гарантирует такую функциональность? Я бы предпочел openmp, но я знаком с большинством других распространенных многопоточных библиотек.
В качестве обходного пути сейчас я использую жесткий барьер на контрольной точке B и реструктурировал свой код, чтобы заставить работу между контрольной точкой A и B автоматически распределять нагрузку между потоками (что иногда было довольно сложно ).
Спасибо за любой совет / понимание:)