LINUX.ORG.RU

Сопрограммы: как реализовать sleep_for/sleep_until в сопрограмме?

 ,


2

5

Допустим, я хочу реализовать сопрограмму, в логике которой имеется ожидание, которое эквивалентно http://en.cppreference.com/w/cpp/thread/sleep_until Имеется ли готовый шаблон для такого решения? Если тупо получать текущее время в сопрограмме и делать yield(), если нужный момент не наступил, то получится аналог spinlock - управление будет передаваться по кругу до тех пор, пока для одной из сопрограмм не наступит нужное время. Этого хотелось бы избежать, то есть возвращать управление сопрограмме в нужный момент времени, а неиспользованное время отдавать ОС с помощью того же sleep_until. Высокого разрешения по времени и какой-то большой точности при этом не требуется - сотых долей секунды хватит с головой.

P.S. Подразумевается Boost.Coroutine, но можно и что-то другое, только не экзотическое.

★★★★★

Последнее исправление: asaw (всего исправлений: 1)

ты хочешь странного, и сразу по двум причинам:

1. программы со sleep странны сами по себе

2. сопрограммы на то и сопрограммы чтобы не было необходимости спать. но если очень хочется... тебе поможет диспетчер, который знает обо всех других сопрограммах и их физиологических потребностях. таким образом можно не переключаться на сонных, а если спать хотят все — дёрнуть системный sleep

anonymous
()
Ответ на: комментарий от anonymous

Да, нужно было бы, наверное, отдельно обратиться к капитанам, чтоб они не утруждали себя ответами в этот топик.

asaw ★★★★★
() автор топика
Ответ на: комментарий от x3al

Этот вопрос я видел, но проблема вот в чем:

boost.fiber is C++14-only!

Хотелось бы C++11 хотя бы (чтоб всякие недоделанные VS 2013 поддерживали). И вообще это отдельная библиотека. А так да, посмотрю повнимательнее.

asaw ★★★★★
() автор топика
Последнее исправление: asaw (всего исправлений: 2)
Ответ на: комментарий от asaw

Ну хз. Когда ты прикручиваешь к корутинам шедулинг (а «разбудить завтра в 10» это уже шедулинг) — у тебя получается фибер/зелёный тред/как ещё их там называют. Оно сравнительно мелкое.

x3al ★★★★★
()
Ответ на: комментарий от asaw

никто не может запретить тебе походить кругами и более замысловатыми фигурами вокруг трёх сосен перед тем как придти к тому же решению :)

Сопрограммы: как реализовать sleep_for/sleep_until в сопрограмме? (комментарий)

если хочется спать (несмотря на то что ненужно) — добро пожаловать в явную диспетчеризацию (ака шедулинг)

anonymous
()
Ответ на: комментарий от asaw

ничего удивительного

на оффтопике не нужно, потому что есть свои фиберы из коробки, а posix не предоставляет вменяемые переносимые способы реализации, поэтому большинство на практике костылит на ассемблере.

anonymous
()
Ответ на: ничего удивительного от anonymous

а posix не предоставляет вменяемые переносимые способы реализации, поэтому большинство на практике костылит на ассемблере.

Ты этот топик читал прежде чем в него срать?

asaw ★★★★★
() автор топика

asio, наверное, нужно использовать

anonymous
()

А у вас шедулингом короутин кто занимается?

Или такого вообще нет и ваши короутины сами друг другу управление передают?

eao197 ★★★★★
()
Ответ на: комментарий от asaw

с чем именно ты не согласен?

posix не предоставляет

или

большинство на практике

anonymous
()
Ответ на: комментарий от eao197

сопрограммы на то и сопрограммы что всё сами :)

если не сами, то это уже фиберы (в терминологии оффтопика)

anonymous
()
Ответ на: комментарий от anonymous

Да пофигу на терминологию. Если у ТС одна рабочая нить на которой совместно живут короутины, то вопрос в том, кто занимается диспетчеризацией этих короутин. Если они сами себя снимают и толкают на исполнение другую короутину, то у ТС одна проблема. Если есть какой-то шедулер (например, как в инструменте Synca), то совсем другая. И на каждую проблему нужно искать свое решение.

eao197 ★★★★★
()
Ответ на: комментарий от eao197

дык из-за бардака с терминологией люди и не могут понять друг друга :)

в оп-посте речь идёт о boost.coroutine, которые действительно являются корутинами и переключаются друг на друга сами. в комментариях упомянули пропихиваемую прямо сейчас (в очередной раз :) ) обёртку над ними: boost.fiber с таймерами и шедулингом.

anonymous
()
Ответ на: комментарий от anonymous

диспетчером (как предлагается в boost.fiber, насколько я понял) может быть и обычная корутина. мы давеча слегка затронули эту тему в «анонсе» «продукта» ;)

anonymous
()
Ответ на: комментарий от anonymous

boost.coroutine, которые действительно являются корутинами и переключаются друг на друга сами

Не обязательно сами.

asaw ★★★★★
() автор топика
Ответ на: комментарий от eao197

Пока ещё ничего нет, пока всё на стадии проектирования. Но можно предположить, что будет некий контейнер, в котором будут жить экземпляры сопрограмм, из которого некий диспетчер (планировщик) будет по очереди вызывать каждую.

asaw ★★★★★
() автор топика
Ответ на: комментарий от anonymous

в оп-посте речь идёт о boost.coroutine, которые действительно являются корутинами и переключаются друг на друга сами.

Мне тоже так показалось. Но лучше было бы перепроверить.

eao197 ★★★★★
()
Ответ на: комментарий от asaw

Посмотрите тогда на Synca, там уже есть какой-то планировщик, который поддерживает переключение короутин при их ожидании на примитивах синхронизации (там свой вариант mutex-а для использования в короутинах). Плюс еще и таймеры есть. Можно ли на их основе делать sleep_for/sleep_until не знаю.

Автор Synca русскоязычный, очень толковый, вменяемый и приятный в общении товарищ.

Если же будете делать свой планировщик, то вам потребуется что-то в духе менеджера таймеров. Короутина вызывает sleep_for, в менеджер таймеров добавляется заявка, управление отдается планировщику. Планировщик либо запускает следующую готовую к работе короутину, либо же обнаруживает, что все ждут таймеров. Тогда планировщик спрашивает у менеджера таймера когда должен сработать ближайший таймер и сам уходит в sleep_for на это время.

eao197 ★★★★★
()
Ответ на: комментарий от eao197

Если же будете делать свой планировщик, то вам потребуется что-то в духе менеджера таймеров.

В качестве идеи для самостоятельного велосипедостроения у меня была такая: сопрограмма возвращает некий статус, в котором присутствует время следующего запуска. Далее формируется словарь с ключом из времени запуска, в котором в качестве значения выступает ссылка на сопрограмму; словарь отсортирован по времени запуска. Далее диспетчер по очереди вызывает сопрограммы из этого словаря и формирует новый, и т.д. на каждом шаге цикла. Однако, закрались сомнения, что при нескольких тысячах сопрограмм это будет эффективно работать.

asaw ★★★★★
() автор топика
Ответ на: комментарий от asaw

Не вижу принципиальных отличий от того варианта, что я описал сообщением выше.

eao197 ★★★★★
()
Ответ на: комментарий от anatoly

Вот тут вот: http://www.boost.org/doc/libs/1_59_0/libs/coroutine/doc/html/coroutine/motiva... Есть гораздо бллее наглядный пример с Boost.Asio. Только я не понимаю как поддержка Boost.Coroutine в Boost.Asio может решить поставленную задачу. Можно пример?

asaw ★★★★★
() автор топика
Ответ на: комментарий от asaw
#include <iostream>
#include <boost/asio/io_service.hpp>
#include <boost/asio/spawn.hpp>
#include <boost/asio/deadline_timer.hpp>
#include <boost/date_time/posix_time/posix_time.hpp>

using namespace boost::asio;
using namespace boost::posix_time;

io_service ioservice;

int main()
{
    spawn(ioservice, [](yield_context yield) {
        spawn(ioservice, [](yield_context yield) {
            deadline_timer timer(ioservice, seconds(5));
            timer.async_wait(yield);
            std::cout << "coroutine_A\n";
        });
        spawn(ioservice, [](yield_context yield) {
            std::cout << "coroutine_B\n";
        });
    });
    ioservice.run();
    return 0;
}
anatoly
()
Ответ на: комментарий от anatoly

Да, с виду оно. Спасибо.

asaw ★★★★★
() автор топика
Ответ на: комментарий от anonymous

...но предполагается, что они переключаются через общий диспетчер (пусть, например, в boost::asio::io_service)

anonymous
()

Решение anatoly, как оказалось, неплохо масштабируется на десятки тысяч сопрограмм:

#include <boost/asio/io_service.hpp>
#include <boost/asio/spawn.hpp>
#include <boost/asio/high_resolution_timer.hpp>
#include <boost/chrono/chrono.hpp>
#include <iostream>

int main()
{
    boost::asio::io_service ioservice;

    boost::chrono::high_resolution_clock::duration maxDiff(boost::chrono::high_resolution_clock::duration::zero()),
        cumulativeDelay(boost::chrono::high_resolution_clock::duration::zero());
    size_t count = 0;

    for (int coro = 1; coro != 10000; ++coro) {
        boost::asio::spawn(ioservice,
            [coro, &ioservice, &maxDiff, &cumulativeDelay, &count](boost::asio::yield_context yield) {
            boost::asio::basic_waitable_timer<boost::chrono::high_resolution_clock> timer(ioservice);
            std::cout << "Coroutine " << coro << std::endl;
            for (size_t i = 0; i != 10; i++) {
                auto seconds = coro % 10;
                std::cout << "Coroutine " << coro << ": iteration " << i
                          << "; waiting " << seconds << " seconds..." << std::endl;
                timer.expires_from_now(boost::chrono::seconds(seconds));
                timer.async_wait(yield);
                auto diff = boost::chrono::high_resolution_clock::now() - timer.expires_at();
                cumulativeDelay += diff;
                ++count;
                if (maxDiff < diff)
                    maxDiff = diff;
                std::cout << "Coroutine " << coro << ": iteration " << i <<  "; finished waiting " << seconds
                          << " seconds. The delay was "
                          << boost::chrono::duration_cast<boost::chrono::milliseconds>(diff).count()
                          << " milliseconds." << std::endl;
            }
            std::cout << "Coroutine " << coro << ": exiting..." << std::endl;
            });
    }

    ioservice.run();

    std::cout << "All done.\n"
              << "Worst delay was "
              << boost::chrono::duration_cast<boost::chrono::milliseconds>(maxDiff).count()
              << " milliseconds.\n"
              << "Mean delay was "
              << boost::chrono::duration_cast<boost::chrono::milliseconds>(cumulativeDelay).count() / count
              << " milliseconds."
              << std::endl;

    return 0;
}
...
All done.
Worst delay was 1207 milliseconds.
Mean delay was 11 milliseconds.

В общем, то, что нужно.

asaw ★★★★★
() автор топика
Последнее исправление: asaw (всего исправлений: 2)
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.