Я пытаюсь реализовать циклический барьер в C ++ с нуля. Цель состоит в том, чтобы реализовать как можно более полную реализацию Java. Класс
ссылка здесь. https://docs.oracle.com/javase/7/docs/api/java/util/concurrent/CyclicBarrier.html
Теперь в моем тестировании returnStatus должен быть для каждого потока, который успешно отключает барьер, значение от барьерного ограничения-1 до нуля. Я пытаюсь добиться этого, используя атомарные переменные и забор памяти. но мой код не проходит тестирование, и в некоторых случаях два потока имеют одинаковое значение returnStatus.
Кто-нибудь может предложить, если какой-либо метод может быть полезен для решения этой проблемы. Я хочу решить эту проблему без использования блокировок, чтобы я мог по-настоящему применять поведение без блокировки в максимально возможной степени.
Полная ссылка на код: https://github.com/anandkulkarnisg/CyclicBarrier/blob/master/CyclicBarrier.cpp
Sample test case result is below [ buggy case ]:
I am currently in thread id = 140578053969664.My barrier state count is = 4
I am currently in thread id = 140577877722880.My barrier state count is = 2
I am currently in thread id = 140577550407424.My barrier state count is = 1
I am currently in thread id = 140577936471808.My barrier state count is = 2
I am currently in thread id = 140577760225024.My barrier state count is = 0The code snippet is below.
// First check and ensure that the barrier is in good / broken state.
if(!m_barrierState && !m_tripStatus)
{
// First check the status of the variable and immediately exit throwing exception if the count is zero.
int returnResult;
if(m_count == 0)
throw std::string("The barrier has already tripped. Pleas reset the barrier before use again!!" + std::to_string(returnResult));
// First ensure that the current wait gets the waiting result assigned immediately.
std::atomic_thread_fence(std::memory_order_acquire);
m_count.fetch_sub(1, std::memory_order_seq_cst);
returnResult = m_count.load();
std::atomic_thread_fence(std::memory_order_release);
std::atomic_thread_fence(std::memory_order_acquire);
m_count.fetch_sub(1, std::memory_order_seq_cst); // [1]
returnResult = m_count.load(); // [2]
std::atomic_thread_fence(std::memory_order_release);
[2]
несколько потоков делают этот шаг одновременно. std::atomic_thread_fence
не мешает другим потокам запускать один и тот же код одновременно. Вот так 2 потока могут заканчиваться одним и тем же значением.
Вместо этого поймайте возвращаемое значение fetch_sub
на линии, отмеченной [1]
returnResult = m_count.fetch_sub(1, std::memory_order_seq_cst) - 1;
Кстати, я почти уверен, что тебе здесь не нужны заборы. (Я не могу сказать, не видя больше функции.) Если вы это сделаете, вы могли бы просто переключиться returnResult
вместо этого быть атомным.
Похоже, вы используете заборы, как если бы они были транзакционной памятью. Они не. Релиз по существу контролирует гарантии упорядочения магазинов при восприятии любым процессором, использующим приобретение. Пока это не нарушает гарантии упорядочения, запись может распространяться до фактической обработки выпуска. В качестве мысленного эксперимента представьте, что [1]
выполняется, затем происходит переключение контекста, проходит миллион лет, затем [2]
выполнен. Сейчас явно нелепо полагать, что m_count
имеет то же значение, что и миллион лет назад. Релиз может очистить буфер записи, но возможно, что изменение уже сброшено.
Наконец, странные вещи могут произойти, если вы смешаете seq_cst
с acquire
/ release
семантика. Извините, что расплывчато, но я не понимаю этого достаточно хорошо, чтобы попытаться объяснить это.
Других решений пока нет …