Я пытаюсь ознакомиться с атомарностью c ++ 11, поэтому я попытался написать барьерный класс для потоков (до того, как кто-то пожалуется на то, что он не использует существующие классы: это больше для обучения / самосовершенствования, чем из-за какой-либо реальной необходимости) ). мой класс выглядит в основном следующим образом:
class barrier
{
private:
std::atomic<int> counter[2];
std::atomic<int> lock[2];
std::atomic<int> cur_idx;
int thread_count;
public:
//constructors...
bool wait();
};
Все члены инициализируются в ноль, кроме thread_count, который содержит соответствующий счетчик.
Я реализовал функцию ожидания как
int idx = cur_idx.load();
if(lock[idx].load() == 0)
{
lock[idx].store(1);
}
int val = counter[idx].fetch_add(1);
if(val >= thread_count - 1)
{
counter[idx].store(0);
cur_idx.fetch_xor(1);
lock[idx].store(0);
return true;
}
while(lock[idx].load() == 1);
return false;
Однако при попытке использовать его с двумя потоками (thread_count
равно 2) первый поток попадает в цикл ожидания просто отлично, но второй поток не снимает барьер (кажется, он даже не доходит до int val = counter[idx].fetch_add(1);
, но я не слишком уверен в этом. Однако, когда я использую gcc atomic-intrinsics, используя volatile int
вместо std::atomic<int>
и пишу wait
следующим образом:
int idx = cur_idx;
if(lock[idx] == 0)
{
__sync_val_compare_and_swap(&lock[idx], 0, 1);
}
int val = __sync_fetch_and_add(&counter[idx], 1);
if(val >= thread_count - 1)
{
__sync_synchronize();
counter[idx] = 0;
cur_idx ^= 1;
__sync_synchronize();
lock[idx] = 0;
__sync_synchronize();
return true;
}
while(lock[idx] == 1);
return false;
работает просто отлично. Насколько я понимаю, между этими двумя версиями не должно быть принципиальных различий (более конкретно, если что-нибудь будет работать с меньшей вероятностью). Итак, какой из следующих сценариев применим?
- Мне повезло со второй реализацией, и мой алгоритм - дерьмо
- Я не до конца понял
std::atomic
и есть проблема с первым вариантом (но не со вторым)
- Это должно работать, но экспериментальная реализация для библиотек c ++ 11 не настолько развита, как я надеялся
Для записи я использую 32bit MINGW с GCC 4.6.1
Код вызова выглядит следующим образом:
spin_barrier b(2);
std::thread t([&b]()->void
{
std::this_thread::sleep_for(std::chrono::duration<double>(0.1));
b.wait();
});
b.wait();
t.join();
Поскольку mingw не отбивает <thread>
jet заголовков, я использую самостоятельно написанную версию для того, что в основном оборачивает соответствующие функции pthread (прежде чем кто-то спросит: да, это работает без барьера, так что это не должно быть проблемой с упаковка)
Любые идеи будут оценены.
edit: Объяснение алгоритма, чтобы сделать его более понятным:
thread_count
- количество нитей, которые должны ожидать барьер (поэтому, если thread_count
нитей находятся в барьере, все могут покинуть барьер).
lock
устанавливается в единицу, когда первая (или любая) нить входит в барьер.
counter
подсчитывает, сколько нитей находится внутри барьера и атомно увеличивается на единицу для каждой нити
if counter>=thread_count
все нити находятся внутри барьера, поэтому счетчик и блокировка обнуляются
- в противном случае поток ожидает, пока
lock
станет нулевым
- при следующем использовании барьера используются различные переменные (
counter
, lock
), чтобы гарантировать отсутствие проблем, если потоки все еще ожидают первого использования барьера (например, они были прерваны, когда барьер снят)
edit2:
Сейчас я проверил его, используя gcc 4.5.1 под linux, где обе версии работают нормально, что, похоже, указывает на проблему с std::atomic
в mingw, но я до сих пор не совсем убежден, так как изучаю <atomic>
заголовок гласил, что большинство функций просто вызывают соответствующий gcc-атомарный смысл, между двумя версиями действительно не должно быть различий