Я пытаюсь ознакомиться с атомикой 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
и записывая 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
и есть проблема с первым вариантом (но не со вторым)Для справки, я использую 32-битный 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 не имеет заголовков
jet, я использую для этого самописную версию, которая в основном обертывает соответствующие функции pthread (прежде чем кто-то спросит: да, это работает без барьера, так что с обертыванием проблем быть не должно).
Любые соображения будут приняты с благодарностью.
edit: Пояснение к алгоритму, чтобы было понятнее:
thread_count
- количество потоков, которые должны ждать барьера (так что если thread_count
потоков находятся в барьере, все могут покинуть барьер). lock
устанавливается в единицу, когда первый (или любой) поток входит в барьер. counter
считает, сколько потоков находится внутри барьера, и атомарно увеличивается один раз для каждого потокаесли counter>=thread_count
, все потоки находятся внутри барьера, поэтому counter и lock обнуляютсяlock
станет нулемcounter
, lock
) используются для того, чтобы не возникало проблем, если потоки все еще ожидают первого использования барьера (например. например, они были вытеснены при снятии барьера)edit2:
Теперь я протестировал это, используя gcc 4.5.1 под linux, где обе версии, похоже, работают просто отлично, что, похоже, указывает на проблему с std::atomic
от mingw, но я все еще не до конца уверен, так как изучение заголовка
подтвердило, что большинство функций просто вызывают соответствующий gcc-atomic, а значит, разницы между двумя версиями быть не должно