Я попытался реализовать простой барьер в своем коде, который выглядит так:
void waitOnBarrier(int* barrier, int numberOfThreads) {
atomicIncrement(barrier); // atomic increment implemented in assembly
while(*barrier < numberOfThreads);
}
И затем в коде есть использование барьера:
int g_barrier = 0; // a global variable
waitOnBarrier(&g_barrier, someKnownNumberOfThreads);
Пока все хорошо, но где я должен обнулить переменную g_barrier? Если я напишу что-то вроде
g_barrier = 0;
сразу после вызова waitOnBarrier у меня возникнет проблема, если один из потоков выйдет из барьера быстрее других и аннулирует g_barrier, в то время как все остальные потоки продолжают выполняться инструкции цикла, так что в конце концов они застрянут на барьере навсегда.
Объяснение: waitOnBarrier скомпилируется примерно так (псевдокод):
1: mov rax, numberOfThreads
2: mov rbx, [barrier]
3: cmp rax, rbx
4: jmp(if smaller) to 2
Итак, если у нас есть 2 потока, синхронизирующихся на барьере, и thread_1 медленный где-то в инструкции 3 или 4, в то время как более быстрый thread_2 достигает барьера, проходит его и переходит к Поток аннулирования g_barrier. Это означает, что после того, как thread_1 достигнет инструкции 2, он увидит нулевое значение на [барьере] и навсегда застрянет на барьере!
Вопрос в том, как мне обнулить g_barrier, какое место для него в коде "достаточно далеко", чтобы я мог быть уверен, что к тому времени все потоки покинули барьер? Или есть более правильный способ реализовать барьер?
atomic_flag
с операциями проверки и установки для реализации ожидания занятости. Не изобретайте велосипед и сделайте свой код перспективным. - person Jens Gustedt   schedule 07.10.2014