LINUX.ORG.RU

История изменений

Исправление dissident, (текущая версия) :

По-моему у тебя немного каша в голове (как немного и у меня, для чего я этот топик и создал - чтобы прояснить вопрос):

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

void Coroutine()
{
    while (!Poll())
    {
        yield();
    }
    DoSmth();

}

Конструкции вроде await, позволяют написать вот это, превращая блокирующую функцию в неблокирующую:

void NonBlockingFunctionUsingCoroutineRead()
{
    response = await ReadFromTcp();
    DoSmth(response);
}

Здесь вместо полинга есть отдельный трэд, но тот трэд который его вызывает, вместо callback’ов (как в каком-нибудь javascript):

do_smth((function() {
  do_smth_else(function() {
     do_yet_another_thing();
  });
});

do() {
   do_smth();
}

Вместо этой каши используя корутины можно написать:

do() {
   await do_smth();
   await do_smth_else();
   do_yet_another_thing();
}

Корутины можно использовать и без какого-то трэда «снизу», т.е. не только оборачивая блокирующую операцию read() (правда в этом случае она сама должна быть корутиной а не просто функцией), в этом смысле они похожи на обычную функцию, с той разницей, что передают управление вверх. Стэковые они при этом или безстековые - деталь реализации (определаяющая лишь то, можно ли из одной корутины вызвать другую):

void Coroutine1()
{
    while (true)
    {
        DoSmth1();
        yield();
    }
}

void Coroutine2()
{
    while (true)
    {
        DoSmth2();
        yield();
    }
}

Coroutine1 cor1;
Coroutine2 cor2;

while (true)
{
   cor1.resume();
   cor2.resume();
}

Это равнозначно такому коду:

while (true)
{
   DoSmth1();
   DoSmth2();
}

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

Превратить псевдокод выше в волокна (fibers):

void Coroutine1()
{
    // same as above
}

void Coroutine2()
{
    // same as above
}

SetSomeKindOfManagerWithPrioritiesForExampleAndRun()
{    
    Coroutine1 cor1;
    Coroutine2 cor2;
    cor1.SetPiority(1);
    cor1.SetPiority(2);
    cor1();
    cor2();
}

Т.е. и корутины и волокна - это просто возможность написать однотрэдовый код, в котором передается управление между корутинами/волокнами в точках, заданных пользователем (yield, await и т.п.). Стэковые они при этом или нет - вопрос второй. Я так понимаю, что особого смысла в безстэковых файберах нету, так как это будут просто безстэковые корутины, к котором прилеплен цикл (простой или хитроумный, например с приоритетами, с возможностью выбора типа цикла), который легко написать и руками. Я не вижу проблемы, в том, чтобы написать стэковые корутины, гда одна может вызвать другую, но какой в этом смысл? Вероятно с безстэковыми корутинами и хитроумным шедулером легко создать deadlock (даже если трэд один). Поправьте меня кто-нибудь plz - если это не так?

То, что в boost::fibers использовано похожее API, что и для трэдов (boost::fibers::async например) - просто уровень абстракции, внутри все равно использующий yield.

Ни мутексы, ни тому подобные вещи ни в корутинах, ни в файберах не нужны, так как трэд один (ну или несколько трэдов, в каждом из которых какое-то количество корутин/файберов).

Т.е. сами корутины просто позволяют избежать каши в коде из коллбэков, или машин состояний (FSM), или ручного написания однотрэдовой event queue.

Файберы позволяют задать тип шедулера (какой файбер выбрать, если данный вызвал yield или закончился). То, что они стэковые - позволяет не заморачиваться с тем, кто кого вызывает, а выбрать тип шедулера.

Корутину можно использовать как для того, чтобы ждать блокирующую операцию, так и просто для выполнения разных кусков кода так, чтобы они были в одном трэде, но при этом были написаны «в одном месте». Добавление шедулера и стэковости позволяет использовать корутины практически как трэды (потому в boost::fibers и такое же API как в C++ трэдах).

Корутины можно превратить в файберы добавив шедулер и (вероятно) стэк. Файберы в корутины можно превратить не используя шедулер а переключая их вручную.

Переключение стэка так или иначе будет делаться при помощи запоминания переменных и т.п., разница в том, будет оно рекурсивным или нет. IMHO самое важное здесь то, что в этом случае:

void Function1()
{
   await Coroutine();
}

void Function2()
{
   await Coroutine();
}

void Coroutine()
{
   DoSmthLong();
   yield();
}

И Function1 и Function2 дождались Coroutine(). Без стэка этого осуществить IMHO нельзя. Поправьте меня пожалуйста кто-нибудь если я не прав?

Исходная версия dissident, :

По-моему у тебя немного каша в голове (как немного и у меня, для чего я этот топик и создал - чтобы прояснить вопрос):

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

void Coroutine()
{
    while (!Poll())
    {
        yield();
    }
    DoSmth();

}

Конструкции вроде await, позволяют написать вот это:

void Coroutine()
{
    response = await ReadFromTcp();
    DoSmth(response);
}

Здесь вместо полинга есть отдельный трэд, но тот трэд который его вызывает, вместо callback’ов (как в каком-нибудь javascript:

do_smth((function() {
  do_smth_else(function() {
     do_yet_another_thing();
  });
});

do() {
   do_smth();
}

Вместо этой каши используя корутины можно написать:

do() {
   await do_smth();
   await do_smth_else();
   do_yet_another_thing();
}

Корутины можно использовать и без какого-то трэда «снизу», т.е. не только оборачивая асинхронную операцию read(), в этом смысле они похожи на обычную функцию, с той разницей, что передают управление вверх. Стэковые они при этом или безстековые - деталь реализации (определаяющая лишь то, можно ли из одной корутины вызвать другую):

void Coroutine1()
{
    while (true)
    {
        DoSmth1();
        yield();
    }
}

void Coroutine2()
{
    while (true)
    {
        DoSmth2();
        yield();
    }
}

Coroutine1 cor1;
Coroutine2 cor2;

while (true)
{
   cor1.resume();
   cor2.resume();
}

Это равнозначно такому коду:

while (true)
{
   DoSmth1();
   DoSmth2();
}

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

Превратить псевдокод выше в волокна (fibers):

void Coroutine1()
{
    // same as above
}

void Coroutine2()
{
    // same as above
}

SetSomeKindOfManagerWithPrioritiesForExampleAndRun()
{    
    Coroutine1 cor1;
    Coroutine2 cor2;
    cor1.SetPiority(1);
    cor1.SetPiority(2);
    cor1();
    cor2();
}

Т.е. и корутины и волокна - это просто возможность написать однотрэдовый код, в котором передается управление между корутинами/волокнами в точках, заданных пользователем (yield, await и т.п.). Стэковые они при этом или нет - вопрос второй. Я так понимаю, что особого смысла в безстэковых файберах нету, так как это будут просто безстэковые корутины, к котором прилеплен цикл, который легко написать и руками. Я не вижу проблемы, в том, чтобы написать стэковые корутины, гда одна может вызвать другую, но какой в этом смысл? Вероятно с безстэковыми корутинами и хитроумным шедулером легко создать deadlock (даже если трэд один). Поправьте меня кто-нибудь plz - если это не так?

То, что в boost::fibers использовано похожее API, что и для трэдов (boost::fibers::async например) - просто уровень абстракции, внутри использующий yield.