Здравствуйте, Nuzhny, Вы писали:
AD>>Пытаться сделать очередь, пусть и из двух элементов, для которой ожидание необходимо, но при этом пытаться не ждать (на мьютексе, спин-локе — не важно), несколько странно. N>Не будем забывать, что это учебная задача. Но всё равно не так уж и странно. Можно представить, что у нас Producer получает на вход картинку, на которой тяжёлая нейросеть пытается найти объекты. Вот мы и запускаем её в отдельном потоке, а пока обрабатываем результаты предыдущего распознавания. Подчеркну, что не стоит акцентировать вопрос на предметной области. Кажется, что такая задача весьма общая и может иметь такое же общее решение. Может быть у неё есть общепринятое название? Я бы с удовольствием загуглил, но не знаю что.
Так или иначе, если какой-то результат нельзя отбросить и/или переиспользовать, то ждать придется.
Пытаться не ждать, когда ждать необходимо, просто не получится.
Здравствуйте, andrey.desman, Вы писали:
AD>Тут же цикл, где на каждой итерации запускается поток (std::async), а потом синхронно ожидается его заверешение (answer.wait()). Выигрышь по времени тут только из-за параллельно выполняющихся слипов. В принципе, если в цикле вместо слипа делать что-то осознанное, но если ничего не делать, то прямой вызов будет лучше.
Так в реальности что-то и делается. Один поток генерирует результаты, а второй их обрабатывает. Ускорение до двух раз теоретически возможно просто сделав лаг на 1 итерацию.
Здравствуйте, andrey.desman, Вы писали:
AD>Так или иначе, если какой-то результат нельзя отбросить и/или переиспользовать, то ждать придется. AD>Пытаться не ждать, когда ждать необходимо, просто не получится.
Да, разумеется, я готов ждать. Ещё раз скажу, что хочется решить всё это на современном С++ более высокоуровневыми средствами.
Здравствуйте, Nuzhny, Вы писали:
AD>>Так или иначе, если какой-то результат нельзя отбросить и/или переиспользовать, то ждать придется. AD>>Пытаться не ждать, когда ждать необходимо, просто не получится. N>Да, разумеется, я готов ждать. Ещё раз скажу, что хочется решить всё это на современном С++ более высокоуровневыми средствами.
Так или иначе, там будут потоки, мьютексы и условные переменные. В C++ таких очередей нет, так что или где-то на стороне искать надо, или самому делать.
N> Ещё раз скажу, что хочется решить всё это на современном С++ более высокоуровневыми средствами.
У тебя примитивная задача с двумя очередями (пустые и заполненные буферы). И в обоих направлениях, возможно, придется ждать. Тут не нужны высокоуровневые инструменты — мьютексы и условные переменные решают задачу. Возможно, тебе нужна еще какая-то функциональность. Тогда, может быть смысл в других инструментах.
Если тебе не нравятся мьютексы в явном виде — инкапсулируй их куда-нибудь. Но без мьютексов и условных переменных не получится сделать ожидание, поэтому они будут либо явно, либо неявно.
Возможно, тебе нужно реализовать класс DataGenerator, который в методе run() запустит поток-генератор данных, а в методе get_data() будет возвращать очередной буфер, останавливаясь в ожидании, когда там появятся данные (и передавать освободившийся). Метод stop() будет останавливать поток-генератор. Однозначно, тот код, который ты написал нужно структурировать в классы (я был уверен, что он написан в процедурном стиле, потому что это чисто учебная задача).
Здравствуйте, astel, Вы писали:
A>А вот такое тупое решение не подойдет? A>
A> int buf[2] = { 0 };
A> atomic<size_t> currInd=0;
A> // потом когда надо переключить буфер
A> currInd.exchange(1-currInd);
A>
Вот, кстати, я в своёс старом коде погонял разные сценарии и нашёл гонку в этом месте при переключении буфера. По итогу, оказалось очень классным решение Masterspline с двумя независимыми счётчиками в каждом потоке: они и работают верно, и синхронизации не требуют.
Здравствуйте, Nuzhny, Вы писали:
N>Приветствую! N>Хочу разобраться с тем, как сделать работу с двумя буферами на С++ без явного использования мьютексов в качестве упражнения. Реальный прототип — это два изображения, а не целые числа, с двумя мьютексами и условными переменными у меня работает, но хочется сделать это проще, быстрее и посовременней.
Блин. А что так сложно? если нужно свапать буфера, так сделай два указателя на нужные классы, и атомарный индекс.
Доступ к первому буферу — Buffers[index], к второму — Buffers[!index]
а ещё лучше — сделай массив на три volatile (или atomic) указателя на Buffer. и в них размести первый — второй — первый. Тогда Buffers[index+1] всегда будет второй буфер. Это лучше, потому что Buffers[!index] неатомарная операция, а Buffers[index+1] атомарна при переводе в асм. Только нужно смотреть на барьеры памяти.
а если надо свапнуть — с помощью атомарной функции заменяешь index на !index;
а если уж свапать без lock-free, то на спинлоках, а не на мьютексах. это же короткая операция — три присвоения укзаателя. мьютекс на мелких операциях расточителен, имхо.
Здравствуйте, Nuzhny, Вы писали:
N>Как обычно в этих случаях и работает: в один буфер пишем со звуковой карты, а другой слушаем — потом меняем, в один буфер рисуем, а другой выводим на экран — потом меняем. Приём старый, решается мьютексами и condition variable, а мне интересно без них.
Так-то зачастую тремя, первый выводится, в два другие рендерятся (чтобы не ждать, пока первый станет доступный для заполнения). А ещё всё активнее проникают технологии типа variable refresh rate, которые, при должном стечении обстоятельств позволяют одним обходиться, а то и просто рабочим кешем.