Мир без корутин. Костыли для программиста — asyncio

    1. Введение


    Тот, кто научился летать, ползать уже не будет. Но не должно быть и высокомерия к тому, кто «летать не может» в принципе. И то и другое вполне норма. И то и другое уважаемо и почетно. Для человека — это, как выбор профессии: вы, условно, либо летчик, либо шофер. Для тех же животных аналогично — вы либо орел, либо волк, т.е. либо летаете, либо бегаете (убегаете). Но только человек в своих понятиях, категориях, отношении и мыслях наделил персонажи характеристиками и выработал свое отношение к ним. Правда, с нюансами. Так, нет, наверное, почетнее и романтичнее профессии летчика, но попробуйте в этом убедить дальнобойщика или авиаконструктора?! И тут сложно возразить: космонавтов много даже сейчас, а второго Королева все еще нет!

    Мы — программисты. Может, в разной степени, но некоторые — уж точно. Это я к тому, что мы разные и мыслить можем тоже по-разному. Утверждение, что программист мыслит только последовательно, столь же однобоко, вредно и даже кощунственно, как и то, что человек только бегает. Он иногда — и летает. Кто-то, как летчики, делает это довольно регулярно, а некоторые, как космонавты, даже месяцами и непрерывно. Идея последовательного мышления принижает способности человека. В какой-то момент и на какое-то время в это можно даже поверить, но " все-таки она вертится" — это про то, что рано или поздно жизнь возьмет свое.

    Asyncio в Python — это «программные костыли», имитирующие, образно выражаясь, «полет» неправильного параллельного мышления. Этакое подпрыгивание со взмахами рук. Выглядит, порой, смешно и коряво. Хотя в определенной ситуации это тоже выход: можно просто перейти лужу и заляпаться, но, если силы позволяют, то лучше уж перепрыгнуть. Но, может, программистам сил не хватает?

    Попробуем отбросить навязываемые «программные костыли» и воспарить над программной обыденностью. И пусть это будет не прыжок, а, может, не такой уж высокий и длительный, но все же, особенно в сравнении с костылями, полет. Ведь когда-то и Можайский Александр Федорович (не путать с Можайским городским судом Московской области ;) или те же братья Райт преодолели по воздуху впервые несколько сот метров. Да и испытания современных самолетов начинают с пробежки и кратковременного отрыва от взлетной полосы.

    2. Совсем простой пример с asyncio


    А начнем мы с полетов на Python. Программа полетов простая. Есть самолеты (которые, правда, в исходном варианте образности пауки см. [1]) с именами на фюзеляжах «Blog», «News», «Forum». Вылетают они одновременно. Каждый за определенное время должен пролететь отрезок пути и выбросить, предположим, флажок с номером преодоленного отрезка. Так нужно поступить три раза. И только после этого приземлиться.

    На языке Python модель подобного поведения описывает, а затем и моделирует, код листинга 1.

    Листинг 1. Код самолетов-пауков на Python
    import asyncio
    import time
    
    async def spider(site_name):
     for page in range(1, 4):
         await asyncio.sleep(1)
         print(site_name, page)
    
    spiders = [
     asyncio.ensure_future(spider("Blog")),
     asyncio.ensure_future(spider("News")),
     asyncio.ensure_future(spider("Forum"))
    ]
    
    start = time.time()
    
    event_loop = asyncio.get_event_loop()
    event_loop.run_until_complete(asyncio.gather(*spiders))
    event_loop.close()
    
    print("{:.2F}".format(time.time() - start))
    


    Результаты моделирования подобного «полета» следующие:

    Blog 1
    News 1
    Forum 1
    Blog 2
    News 2
    Forum 2
    Blog 3
    News 3
    Forum 3
    3.00

    Почему это так подробно разъясняет видео [1]. Но мы-то ребята с фантазией и одновременный (по сценарию — асинхронный) полет наших трех «самолетов» без использования asyncio представим по-другому — на базе автоматных моделей. Так, листинг 2 приводит код автоматной задержки — аналога асинхронной задержки из модуля asyncio, представленной строкой await asyncio.sleep(1) в листинге 1.

    Листинг 2. Код автоматной задержки на Python
    import time
    
    class PSleep:
        def __init__(self, t, p_FSM): self.SetTime = t; self.nState = 0; self.bIfLoop = False; self.p_mainFSM = p_FSM
        def x1(self): return time.time() - self.t0 <= self.SetTime
        def y1(self): self.t0 = time.time()
        def loop(self):
            if (self.nState == 0): self.y1(); self.nState = 1
            elif (self.nState == 1):
                if (not self.x1()): self.nState = 4
    


    Через конструктор класса передается значение задержки и указатель на объект, создавший объект задержки. Указатель требуется функции управления процессами, которая после удалении задержки продолжит работу родительского процесса, который был остановлен при ее создании.

    На листинге 3 показан автоматный аналог асинхронного «самолета-паука» (см. также листинг 1). Весьма вероятно, что асу программирования на Python подобное не приснится даже в самом кошмарном сне! Исходный код из четырех строчек увеличился в 15 раз! Это ли не повод восхищения типичным кодом Python вообще и asycio в частности или, как минимум, доказательство преимущества «корутинной технологии» перед автоматным программированием?

    Листинг 3. Код автоматного паука на Python
    # "паук" для страницы "Blog"
    class PBSpider:
        def __init__(self, name):
            self.nState = 0; self.bIfLoop = True; self.site_name = name; self.page = 1;
            self.p_mainFSM = b_sleep;
        def x1(self): return self.page < 4
        def y1(self):
            self.bIfLoop = False; automaton.append(b_sleep);
            b_sleep.p_mainFSM = blog
            automaton[-1].bIfLoop = True;
            automaton[-1].nState = 0
        def y2(self): print(self.site_name, self.page)
        def y3(self): self.page += 1
        def y4(self): self.page = 1
        def loop(self):
            if (self.x1() and self.nState == 0):  self.y1(); self.nState = 1
            elif (not self.x1()  and self.nState == 0): self.y1(); self.y4(); self.nState = 33
            elif (self.nState == 1): self.y2(); self.y3(); self.nState = 0
    
    # "паук" для страницы "News"
    class PNSpider:
        def __init__(self, name):
            self.nState = 0; self.bIfLoop = True; self.site_name = name; self.page = 1;
            self.p_mainFSM = n_sleep;
        def x1(self): return self.page < 4
        def y1(self):
            self.bIfLoop = False; automaton.append(n_sleep);
            n_sleep.p_mainFSM = news
            automaton[-1].bIfLoop = True;
            automaton[-1].nState = 0
        def y2(self): print(self.site_name, self.page)
        def y3(self): self.page += 1
        def y4(self): self.page = 1
        def loop(self):
            if (self.x1() and self.nState == 0):  self.y1(); self.nState = 1
            elif (not self.x1()  and self.nState == 0): self.y1(); self.y4(); self.nState = 33
            elif (self.nState == 1): self.y2(); self.y3(); self.nState = 0
    
    # паук для страницы "Forum"
    class PFSpider:
        def __init__(self, name):
            self.nState = 0; self.bIfLoop = True; self.site_name = name; self.page = 1;
            self.p_mainFSM = f_sleep;
        def x1(self): return self.page < 4
        def y1(self):
            self.bIfLoop = False; automaton.append(f_sleep);
            f_sleep.p_mainFSM = forum
            automaton[-1].bIfLoop = True;
            automaton[-1].nState = 0
        def y2(self): print(self.site_name, self.page)
        def y3(self): self.page += 1
        def y4(self): self.page = 1
        def loop(self):
            if (self.x1() and self.nState == 0):  self.y1(); self.nState = 1
            elif (not self.x1()  and self.nState == 0): self.y1(); self.y4(); self.nState = 33
            elif (self.nState == 1): self.y2(); self.y3(); self.nState = 0
    
    # задержки
    b_sleep = PSleep(1, 0)
    n_sleep = PSleep(1, 0)
    f_sleep = PSleep(1, 0)
    # "пауки"
    blog = PBSpider("Blog")
    news = PNSpider("News")
    forum = PFSpider("Forum")
    # формирование исходного списка процессов
    automaton = []
    automaton.append(blog);
    automaton.append(news);
    automaton.append(forum);
    start = time.time()
    # управление процессами (аналог event_loop)
    while True:
        ind = 0;
        while True:
            while ind < len(automaton):
                if automaton[ind].nState == 4:
                    automaton[ind].p_mainFSM.bIfLoop = True
                    automaton.pop(ind)
                    ind -=1
                elif automaton[ind].bIfLoop:
                    automaton[ind].loop()
                elif automaton[ind].nState == 33:
                    print("{:.2F}".format(time.time() - start))
                    exit()
                ind += 1
            ind = 0
    


    А вот и результат автоматных полетов:

    News 1
    Forum 1
    Blog 1
    Blog 2
    News 2
    Forum 2
    News 3
    Forum 3
    Blog 3
    3.00

    Но — обсудим. Увеличение объема кода произошло из-за проблем с указателями в Python. В результате пришлось создать класс для каждой страницы, что и увеличило код в три раза. Поэтому правильнее говорить не о 15-ти, а о пятикратном увеличении объема. Более искусный в программировании «Python-летчик» этот недостаток, возможно, сможет даже устранить.

    Но основная причина все же не в указателях. Код на С++, показанный далее, при полной свободе работы с указателями, имеет на один класс даже большее число строк. Причина — в используемой вычислительной модели, языке ее описания и подходов к реализации алгоритмов на ее базе. Рис. 1 демонстрирует обычную модель «самолета-паука» в форме блок-схемы и модель в форме автомата. Можно видеть, что внешне и по качеству это разные модели, хотя и допускающие эквивалентные преобразования. У автоматов есть состояния, у блок-схем их нет и в помине. Автоматы по определению работают в дискретном времени, а блок-схемы об этом и не мечтают. Все это накладывает на реализацию модели определенные обязательства.

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

    Рис. 1. Автоматная и блок-схемная модели самолета-паука
    image

    Но даже на уровне отдельного процесса у моделей есть отличия, которые проецируются на язык и реализацию модели. В силу этих качеств апологеты последовательной блок-схемной модели создали конструкции языка, которые в явном или неявном виде позволяют весьма компактно ее описать. Взять тот же цикл for или хотя бы неявно подразумеваемую последовательность исполнения операторов (действия y1, y2, y3).

    Для блок-схемы в одном квадратике можно без проблем перечислить действия и это не изменит последовательный характер их работы. Если же у автомата заменить переходы в состояниях s2, s3 на цикл при состоянии s1, пометив дугу этими же действиями, то смысл алгоритма изменится, т.к. введет параллелизм в его работу. Поскольку упомянутые действия должны исполнятся строго последовательно, то это и предопределило вид модели автомата (см. рис.1). Конечный автомат — модель, не допускающая «двоемыслия».

    Отсутствие дискретного времени у блок-схем было их преимуществом. Но сейчас это стало их основным недостатком. Это влияет, как это не покажется кощунственным, и на образ мышления. Последовательными языками оправдывают последовательное мышление программистов, отказывая им в другом — в параллельном. Именно этим обосновывают конструкции существующего асинхронного программирования, представляя набор и функциональность операторов того же пакета asyncio. И именно этот подход, как утверждают, позволяет привычные программистам последовательные программы превратить в асинхронные (почти что параллельные).

    Но вернемся к теме статьи и ее образам. Мы хотели свой «самолет» и мы его получили! Полеты или, что точнее, видимые их результаты, несколько отличаются по внешнему виду, но совершенно неотличимы по своей сути. Их можно трактовать так, что флажки подбирались и фиксировались в протоколе в ином порядке, но сами «самолеты» при этом летели, как и должно, т.е. одновременно и одновременно выбрасывали свои флажки. А уж в каком порядке их фиксировали — дело, как говорится, десятое. Главное реализовано и иполнено: последовательность и времена их сбрасывания соответствуют программе полета

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

    3. О проблемах реализации автоматов в Python


    Остановимся подробнее на проблемах, которые породили внешний вид автоматного кода. Последний, что там скрывать, выглядит пока монструозно в сравнении с исходным кодом. Но, заметим, и первый самолет Можайского далеко не был похож на современную «сушку», да и первые автомобили отличаются далеко не в лучшую сторону от любого своего современного аналога. Подчеркну, что проблемы предъявленного автоматного кода во многом связаны с моим текущим пониманием языка Python и, возможно, в меньшей степени с возможностями самого языка.

    Тем не менее первая проблема связана с языком описания автоматной модели. В С++ она решена средствами языка. В Python я таких возможностей не вижу. К сожалению, как сейчас иногда выражаются, от слова совсем. Поэтому за основу был взят метод реализации автоматов на базе управляющих операторов языка if-elif-else. Кроме того, напомним, в ВКП(а), кроме собственно автоматов, для полноценной реализации параллелизма введена теневая память и автоматные пространства. Без этого возможности автоматного программирования весьма ограничены и во многом неполноценны.

    Следующая проблема, которую мы уже упоминали, — указатели. В С++ с ними проблем нет. В рамках ВКП(а) в соответствии с парадигмой ООП создан базовый автоматный класс, от которого порождаются прикладные автоматные классы, а их параметрами могут быть не только указатели, но даже их адреса. Все это позволяет просто, компактно и весьма эффективно описать и реализовать любую задачу, включающую множество параллельных взаимодействующих процессов.

    Далее представлен код эквивалентных рассматриваемому примеру автоматных классов на С++. Код задержки на листинге 4 эквивалентен строке await asyncio.sleep(1) на листинге 1. В графической форме ей соответствует модель автомата FAwaitSleep на рис. 1. Такой и только такой автомат можно считать асинхронным и он не будет тормозить вычислительный поток. Автомат FSleep на этом же рисунке соответствует обычному оператору sleep(). Он проще, но гарантированно разрушит модель дискретного времени из-за действия y1, вызывающего обычную последовательную задержку. А это уже ни куда не годится.

    Листинг 4. Код асинхронной задержки
    // Задержка (в дискретном времени)
    #include "lfsaappl.h"
    #include <QTime>
    
    class FAwaitSleep :
        public LFsaAppl
    {
    public:
        FAwaitSleep(int n);
    protected:
        int x1();
        QTime time;
        int nAwaitSleep;
    };
    
    #include "stdafx.h"
    #include "FAwaitSleep.h"
    
    static LArc TBL_AwaitSleep[] = {
        LArc("s1",		"s1","x1",  "--"),			//
        LArc("s1",		"00","^x1",	"--"),			//
        LArc()
    };
    
    FAwaitSleep::FAwaitSleep(int n):
        LFsaAppl(TBL_AwaitSleep, "FAwaitSleep")
    {
        nAwaitSleep = n; time.start();
    }
    
    int FAwaitSleep::x1() { return time.elapsed() < nAwaitSleep; }
    


    Код «самолета-паука» на С++ демонстрирует листинг 5. Данный код в гораздо большей степени адекватен своей модели, чем блок-схема коду на Python. Особенно, если сравнить таблицу переходов автомата и внешний вид автоматного графа. Это просто разные формы описания одного и того же абстрактного понятия — автомата. Здесь же показано, как передается указатель на родительский класс при создании задержки (см. вызов метода FCall в действии y1)

    Листинг 5. Код самолета-паука, имитирующего чтение страниц сайта
    // "Паук". Имитация страниц сайта
    #include "lfsaappl.h"
    
    class FAwaitSleep;
    class FSpider :
        public LFsaAppl
    {
    public:
        LFsaAppl* Create(CVarFSA *pCVF) { Q_UNUSED(pCVF)return new FSpider(nameFsa); }
        bool FCreationOfLinksForVariables() override;
        FSpider(string strNam);
        virtual ~FSpider(void);
        CVar *pVarStrSiteName;		// имя сайта
        FAwaitSleep *pFAwaitSleep{nullptr};
    protected:
        int x1(); void y1(); void y2(); void y3(); void y4();
        int page{1};
    };
    
    #include "stdafx.h"
    #include "FSpider.h"
    #include "FSleep.h"
    #include "FAwaitSleep.h"
    #include <QDebug>
    
    static LArc TBL_Spider[] = {
        LArc("st","s1","--","--"),		
        LArc("s1","s2","x1","y1"),  // x1- номер<макс.числа страниц; y1-задержка;
        LArc("s2","s3","--","y2"),  // y2- печатать номера страницы;
        LArc("s3","s1","--","y3"),  // y3- увеличить номер страницы
        LArc("s1","st","^x1","y4"), // y4- сбросит номера страницы
        LArc()
    };
    FSpider::FSpider(string strNam):
        LFsaAppl(TBL_Spider, strNam)
    { }
    FSpider::~FSpider(void) { if (pFAwaitSleep) delete pFAwaitSleep; }
    
    bool FSpider::FCreationOfLinksForVariables() {
        pVarStrSiteName = CreateLocVar("strSiteName", CLocVar::vtString, "name of site");
        return true;
    }
    // счетчик страниц меньше заданного числа страниц?
    int FSpider::x1() { return page < 4; }
    // create delay - pure sleep (synchronous function) or await sleep (asynchronous function)
    void FSpider::y1() {
        //sleep(1000);
        // await sleep (asynchronous function)
        if (pFAwaitSleep) delete pFAwaitSleep;
        pFAwaitSleep = new FAwaitSleep(1000);
        pFAwaitSleep->FCall(this);
    }
    void FSpider::y2() {
    #ifdef QT_DEBUG
        string str = pVarStrSiteName->strGetDataSrc();
        printf("%s%d", str.c_str(), page);
        qDebug()<<str.c_str()<<page;
    #endif
    }
    void FSpider::y3() { page++; }
    void FSpider::y4() { page = 1; }
    


    Код, реализующий функции так называемого событийного цикла, отсутствует. В нем просто нет необходимости, т.к. его функции исполняет ядро среды ВКП(а). Оно создает объекты и управляет их параллельным исполнением в дискретном времени.

    4. Выводы


    Краткость не всегда сестра таланта, а иногда еще и признак косноязычия. Правда, отличить одно от другого сразу сложно. Код на Python часто будет короче кода на С++. Но это характерно для простых случаев. Чем сложнее решение, тем эта разница будет меньше. В конце концов даже сложность решения определяется возможностями модели. Автоматная модель гораздо мощнее блок-схемной.

    Автоматы и распараллеливание — это в первую очередь весьма эффективные средства решения проблем сложности, борьбы с нею, а не столько средства увеличения скорости работы программы. Поскольку все это — автоматная модель, параллелизм — сложно реализуемо на Python, то, несмотря на все его фишки, батарейки и много еще чего, меня сложно склонить в его сторону. Я бы больше уделял внимание окружению С++, а не очень оправданному внедрению в него тех же корутин. Модель эта временная и причина ее внедрения во многом вынужденная. А что мы будем делать с этим «костылем», когда будет решена проблема выбора параллельной модели?

    Поэтому, извините, но мои предпочтения все еще на стороне С++. А если учесть область моих профессиональных интересов — промышленные системы так называемого «жуткого» жесткого реального времени, то выбора как такового у меня пока что нет. Да, какое-то окружение, какой-то сервис можно создать, используя Python. Это удобно, это быстро, есть много прототипов и т.д. и т.п. Но ядро решения, его параллельную модель, логику самих процессов — однозначно С++, однозначно автоматы. Здесь автоматы, конечно, главнее и рулят. Но никак не корутины :)

    В дополнение… Посмотрите видео [2], обратив внимание на реализацию модели ракеты. О ней, начиная примерно с 12-й минуты, и повествует видео. Респект лектору за использование автоматов :) А на сладкое предлагается еще одно решение из [3]. Оно в духе асинхронного программирования и asyncio. Собственно с этого примера все и началось — реализация вложенных автоматов в Python. Здесь глубина вложения даже больше, чем в примере, подробно рассмотренном выше. На листинге 6 приведен исходный код и его автоматный аналог на Python. На рис. 2 автоматная модель «чаепития», а на листинге 7 — эквивалентная реализация на С++ для ВКП(а). Сравнивайте, анализируйте, делайте выводы, критикуйте…

    Листинг 6. Читаем и пьем чай асинхронно на Python
    import asyncio
    import time
    
    # # Easy Python. Asyncio в python 3.7 https://www.youtube.com/watch?v=PaY-hiuE5iE
    # # 10:10
    # async def teatime():
    #     await asyncio.sleep(1)
    #     print('take a cap of tea')
    #     await asyncio.sleep(1)
    #
    # async def read():
    #     print('Reading for 1 hour...')
    #     await teatime()
    #     print('...reading for 1 hour...')
    #
    # if __name__ == '__main__':
    #     asyncio.run(read())
    
    class PSleep:
        def __init__(self, t, p_FSM): self.SetTime = t; self.nState = 0; self.bIfLoop = False; self.p_mainFSM = p_FSM
        def x1(self): return time.time() - self.t0 <= self.SetTime
        def y1(self): self.t0 = time.time()
        def loop(self):
            if (self.nState == 0): self.y1(); self.nState = 1
            elif (self.nState == 1):
                if (not self.x1()): self.nState = 4
    
    class PTeaTime:
        def __init__(self, p_FSM): self.nState = 0; self.bIfLoop = False; self.p_mainFSM = p_FSM;
        def y1(self): self.bIfLoop = False; automaton.append(sl); automaton[-1].bIfLoop = True; automaton[-1].nState = 0
        def y2(self): print('take a cap of tea')
        def loop(self):
            if (self.nState == 0):  self.y1(); self.nState = 1
            elif (self.nState == 1): self.y2(); self.nState = 2
            elif (self.nState == 2): self.y1(); self.nState = 3
            elif (self.nState == 3): self.nState = 4
    
    class PRead:
        def __init__(self): self.nState = 0; self.bIfLoop = False;
        def y1(self): print('Reading for 1 hour...')
        def y2(self): self.bIfLoop = False; automaton.append(rt); automaton[-1].bIfLoop = True; automaton[-1].nState = 0
        def loop(self):
            if (self.nState == 0): self.y1(); self.nState = 1
            elif (self.nState == 1): self.y2(); self.nState = 2
            elif (self.nState == 2): self.y1(); self.nState = 33; self.bIfLoop = False
    
    read = PRead()
    rt = PTeaTime(read)
    sl = PSleep(5, rt)
    automaton = []
    automaton.append(read); automaton[-1].bIfLoop = True
    while True:
        ind = 0;
        while True:
            while ind < len(automaton):
                if automaton[ind].nState == 4:
                    automaton[ind].p_mainFSM.bIfLoop = True
                    automaton.pop(ind)
                    ind -=1
                elif automaton[ind].bIfLoop:
                    automaton[ind].loop()
                elif automaton[ind].nState == 33:
                    exit()
                ind += 1
            ind = 0
    


    Рис. 2. Автоматная модель чаепития
    image

    Листинг 7. Читаем и пьем чай асинхронно на С++
    #include "lfsaappl.h"
    
    class FRead :
        public LFsaAppl
    {
    public:
        LFsaAppl* Create(CVarFSA *pCVF) { Q_UNUSED(pCVF)return new FRead(nameFsa); }
        FRead(string strNam);
        virtual ~FRead(void);
    protected:
        void y1(); void y2();  void y3();
        LFsaAppl *pFRealTime{nullptr};
    };
    
    #include "stdafx.h"
    #include "FRead.h"
    #include "FTeaTime.h"
    #include <QDebug>
    
    static LArc TBL_Read[] = {
        LArc("s1","s2","--","y1"),	// Reading for 1 hour...
        LArc("s2","s3","--","y2"),	// Call(TeaTime)
        LArc("s3","s4","--","y1"),	// Reading for 1 hour...
        LArc("s4","s5","--","y3"),	// sleep(5)
        LArc("s5","s1","--","--"),	//
        LArc()
    };
    FRead::FRead(string strNam):
        LFsaAppl(TBL_Read, strNam)
    { }
    FRead::~FRead(void) { if (pFRealTime) delete pFRealTime; }
    
    void FRead::y1() {
    #ifdef QT_DEBUG
        qDebug()<<"Reading for 1 hour...";
    #endif
    }
    void FRead::y2() {
        if (pFRealTime) delete pFRealTime;
        pFRealTime = new FTeaTime("TeaTime");
        pFRealTime->FCall(this);
    }
    void FRead::y3() { FCreateDelay(5000); }
    
    
    #include "lfsaappl.h"
    
    class FTeaTime :
        public LFsaAppl
    {
    public:
        FTeaTime(string strNam);
    protected:
        void y1(); void y2();
    };
    #include "stdafx.h"
    #include "FTeaTime.h"
    #include <QDebug>
    #include "./LSYSLIB/FDelay.h"
    
    static LArc TBL_TeaTime[] = {
        LArc("s1",	"s2","--","y1"),// sleep(1)
        LArc("s2",	"s3","--","y2"),// take a cap of tea
        LArc("s3",	"s4","--","y1"),// sleep(1)
        LArc("s4",	"00","--","--"),//
        LArc()
    };
    
    FTeaTime::FTeaTime(string strNam):
        LFsaAppl(TBL_TeaTime, strNam)
    { }
    
    void FTeaTime::y1() { FCreateDelay(2000); }
    void FTeaTime::y2() {
    #ifdef QT_DEBUG
        qDebug()<<"take a cap of tea";
    #endif
    }
    


    P.S.


    Уже после написания статьи после чтения перевода статьи Йерайна Диаза[4], познакомился с еще одним достаточно интересным и довольно восхищенным взглядом на корутины вообще и asyncio в частности. Несмотря на этот факт и ему подобные, мы все же «пойдем иным путем» :) Согласен только в одном с Робом Пайком (Rob Pike), что «Concurrency Is Not Parallelesm». Конкурентность, можно сказать даже жестче, не имеет вообще ни чего общего с параллелизмом. И примечательно, что Google-переводчик переводит эту фразу как «Параллелизм — это не параллелизм». Мужчина по имени Google, конечно, не прав. Но кто-то же его в этом убедил? :)

    Литература


    1. Shultais Education. 1. Введение в асинхронное программирование. [Электронный ресурс], Режим доступа: www.youtube.com/watch?v=BmOjeVM0w1U&list=PLJcqk6mrJtxCo_KqHV2rM2_a3Z8qoE5Gk, свободный. Яз. рус. (дата обращения 01.08.2020).
    2. Computer Science Center. Лекция 9. async / await (Программирование на Python). [Электронный ресурс], Режим доступа: www.youtube.com/watch?v=x6JZmBK2I8Y, свободный. Яз. рус. (дата обращения 13.07.2020).
    3. Easy Python. Asyncio в python 3.7. [Электронный ресурс], Режим доступа: www.youtube.com/watch?v=PaY-hiuE5iE, свободный. Яз. рус. (дата обращения 01.08.2020).
    4. Йерай Диаз (Yeray Diaz). Asyncio для практикующего python-разработчика. [Электронный ресурс], Режим доступа: www.youtube.com/watch?v=PaY-hiuE5iE, свободный. Яз. рус. (дата обращения 01.08.2020).
    AdBlock похитил этот баннер, но баннеры не зубы — отрастут

    Подробнее
    Реклама

    Комментарии 31

      +4

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

        +2
        Я так и не понял, зачем реализовывать параллелизм на asyncio, когда он про конкурентность? А потом это упоминать в PS, тем самым перечеркивая всю статью по сути.
          +5
          О боже! «Горшочек, не вари!». Остановите кто-нибудь этот «автоматный генератор статей» похожих на написанное человеком.
          Самолёты-пауки? Что это? Неужели нельзя было просто оставить «тех.задание» исходного примера, который вы позаимствовали, без изменений? Зачем притягивать за уши «самолёты», на код обычного «паука», который скачивает страницы из интернета?
          Зачем вы вообще используете Python? Вы его абсолютно не знаете. Я даже уже сомневаюсь, что вы вообще программировать умеете. Была бы у меня возможность, я бы отобрал у вас питон и не давал пока не попросите прощения изучите его нормально по книгам и документации.
          Я ещё в прошлой статье обратил ваше внимание на то, что вы используете в методах классов глобальные переменные. Но вы продолжаете писать абсолютную ересь, доводя её до абсурда. Теперь вы создали 3 одинаковых класса, которые используют внутри своих методов глобальный экземпляр ЭТОГО же самого класса. Как вообще у вас такая идея пришла в голову? Сами же потом оправдываете всё это незнанием языка и указателями (которых вообще в питоне, как языке программирования, нет). Прекратите писать о том, о чём не знаете. Изучите сначала язык, наберитесь в нём опыта, и только потом показывайте на публику свой код на нём.
          И что вы вообще пытаетесь доказать своими статьям? Что автоматы — это какое-то лучшее решение чем async/await?
          Только вот реализация async/await в питоне, в своей основе, сделана на генераторах. Генераторы — это просто синтаксический сахар для удобного написания итераторов. Итератор — это самый настоящий автомат. Генераторы позволяют проще писать и понимать код такого автомата, т.к. этот код не размазывается без необходимости по нескольким методам класса.
          В итоге получается что код с async/await — это автомат. Просто он записан более понятным для людей образом без лишней «воды» в коде.
            0
            О боже! «Горшочек, не вари!».
            Давайте снизим накал эмоций…
            Как вообще у вас такая идея пришла в голову?
            Вот так — взяла и посетила :)
            Но, если серьезно. Я в прошлый раз Вас поблагодарил, т.к. Вы, действительно, мне помогли, подтолкнули к пониманию Python. Хотелось бы, чтобы мы такие приятные моменты приумножали :)
            Скажу спасибо и в этот раз. За стимул :) Мне пришла в голову еще одна идея. Вот как она выглядит в коде:
            Заголовок спойлера
            import time
            automaton = []
            
            class PSleep:
                def __init__(self, t, p_FSM): self.SetTime = t; self.nState = 0; self.bIfLoop = True; self.p_mainFSM = p_FSM
                def x1(self): return time.time() - self.t0 <= self.SetTime
                def y1(self): self.t0 = time.time()
                def loop(self):
                    if (self.nState == 0): self.y1(); self.nState = 1
                    elif (self.nState == 1):
                        if (not self.x1()): self.nState = 4
            
            class PBSpider:
                def __init__(self, name, sleep):
                    self.nState = 0; self.bIfLoop = True; self.site_name = name; self.page = 1; self.n_sleep = sleep
                def x1(self): return self.page < 4
                def y1(self):
                    self.bIfLoop = False;
                    self.b_sleep = PSleep(self.n_sleep, 0)
                    automaton.append(self.b_sleep);
                    self.b_sleep.p_mainFSM = automaton[ind]
                def y2(self): print(self.site_name, self.page)
                def y3(self): self.page += 1
                def y4(self): self.page = 1
                def loop(self):
                    if (self.x1() and self.nState == 0):  self.y1(); self.nState = 1
                    elif (not self.x1()  and self.nState == 0): self.y1(); self.y4(); self.nState = 33
                    elif (self.nState == 1): self.y2(); self.y3(); self.nState = 0
            
            class PAirplane:
                def __init__(self, name, n):
                    self.obj = PBSpider(name, n);
                    automaton.append(self.obj)
            
            PAirplane("Blog", 1)
            PAirplane("News", 2)
            PAirplane("Forum", 2)
            
            start = time.time()
            while True:
                ind = 0;
                while True:
                    while ind < len(automaton):
                        if automaton[ind].nState == 4:
                            automaton[ind].p_mainFSM.bIfLoop = True
                            automaton.pop(ind)
                            ind -=1
                        elif automaton[ind].bIfLoop:
                            automaton[ind].loop()
                        elif automaton[ind].nState == 33:
                            print("{:.2F}".format(time.time() - start))
                            exit()
                        ind += 1
                    ind = 0
            


            Здесь я учел Ваши пожелания. Теперь уже ОДИН класс и глобальных переменных меньше. Открою даже свой небольшой секрет — на С++ я вообще не использую глобальных переменных. Их вред мне известен и понятен. И с повторами кода на С++ у меня тоже нормально ;)
            По поводу знаний… Да, я не очень знаю Python. Наверное, в сравнении с Вашим опытом и знаниями так от слова совсем ;) И когда получаю дельные советы и помощь, то весьма благодарен…
            И я не то, что сомневаюсь, а фактически уверен, что Вы не понимаете автоматы. Но как-то не укоряю Вас за это. Да и развеять на этот счет мое мнение можно достаточно просто — нарисуйте граф автомата генератора. Получится — тогда я извинюсь за свою самоуверенность.
            Т.е. давайте от эмоций переключимся на деловой стиль общения.
            А так еще раз спасибо. Хотя Вы и не подсказали, но Ваш «толчок», видимо, повлиял на идею, которая меня и посетила в этот раз. Хотелось бы узнать Вашу оценку ее.
              0

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


              Теперь у вас глобальная переменная automaton, и бесполезный класс PAirplane с сайд-эффектом в конструкторе (он меняет переменную automaton). Мне казалось что "класс ради класса" это особенность разработчиков на C# (там по другому нельзя) и в Java тоже любят всё делать классами, но даже в этом случае конструктор с сайд-эффектом — это привилегия новичков.
              И ещё есть хитро спрятанная глобальная переменная ind, которая не первый взгляд просто какой-то счётчик внутри "event-loop-а", а на деле оказывается используется внутри метода класса PBSpider. Даю вам 5 балов за навык "минирования кода".
              И ещё +1 бал за константы 4 и 33 для поля nState. Они мне нравятся, надо бы тоже почаще их везде пихать. Ещё я люблю 7. Я ведь могу в вашем коде поменять 4 на 7? Мне лично так понятнее будет, а то я 4 обычно для других целей использую.
              Вы не добавили поле p_mainFSM в класс PBSpider, но при этом используете его в "евент-лупе". Но я вас понимаю — там же есть волшебная константа 4, которая железобетонно гарантирует, что мы сейчас имеем дело с экземпляром PSleep. Ведь все знают, что нельзя использовать 4 как значение состояния в других "автоматах" кроме PSleep.


              В ваш код на C++ я даже не стал детально вникать. Для начала там где-то за кадром есть какой-то непонятный ВКП(а). Интернет предложил мне только "Всесоюзная Коммунистическая Партия".
              Далее там какая-то шифровка в виде массивов двухсимвольных строк. И нет кода для его декодирования.
              Ну и по мелочи — непонятно от куда взявшийся метод pFAwaitSleep->FCall(), которого нет в классе FAwaitSleep.


              Что значит "блок схема генератора"? А какая блок схема у автомата? Абстрактного автомата.

                0

                Снимаю своё замечание про метод FCall — не заметил, что у класса есть базовый класс.

                  0
                  То-то же… А то я уж, каюсь, плохо про Вас подумал :)
                  Вот еще великолепный вариант:
                  Заголовок спойлера
                  import time
                  
                  class PSleep:
                      def __init__(self, t):
                          self.bIfLoop = True; self.p_mainFSM = 0; self.nState = 0;
                          self.SetTime = t;
                      def x1(self): return time.time() - self.t0 <= self.SetTime
                      def y1(self): self.t0 = time.time()
                      def loop(self):
                          if (self.nState == 0): self.y1(); self.nState = 1
                          elif (self.nState == 1):
                              if (not self.x1()): self.nState = 666
                  
                  class PBSpider:
                      def __init__(self, name, sleep, p_evl):
                          self.p_EventLoop = p_evl; self.bIfLoop = True; self.p_mainFSM = 0; self.nState = 0;
                          self.site_name = name; self.n_sleep = sleep
                          self.page = 1;
                      def x1(self): return self.page < 4
                      def y1(self):
                          self.bIfLoop = False;
                          self.b_sleep = PSleep(self.n_sleep)
                          self.p_EventLoop.automaton.append(self.b_sleep);
                          self.b_sleep.p_mainFSM = self.p_EventLoop.automaton[self.p_EventLoop.ind]
                      def y2(self): print(self.site_name, self.page)
                      def y3(self): self.page += 1
                      def y4(self): self.page = 1
                      def loop(self):
                          if (self.x1() and self.nState == 0):  self.y1(); self.nState = 1
                          elif (self.nState == 1): self.y2(); self.y3();   self.nState = 0
                          elif (not self.x1()  and self.nState == 0):      self.nState = 666
                  
                  class PAirplane:
                      def __init__(self, name, n, p_evl):
                          self.p_EventLoop = p_evl
                          self.obj = PBSpider(name, n, self.p_EventLoop);
                          self.p_EventLoop.automaton.append(self.obj)
                  
                  class PEventLoop:
                      def __init__(self): self.automaton = []; self.ind = 0
                      def add(self, p_tsk):
                          self.automaton.append(p_tsk);
                      def run_until_complete(self):
                          while True:
                              if len(self.automaton) != 0:
                                  while self.ind < len(self.automaton):
                                      if self.automaton[self.ind].nState == 666:
                                          if (self.automaton[self.ind].p_mainFSM != 0):
                                              self.automaton[self.ind].p_mainFSM.bIfLoop = True
                                              self.automaton.pop(self.ind)
                                              self.ind -= 1
                                          else:
                                              self.automaton.pop(self.ind)
                                              self.ind -= 1
                                      elif self.automaton[self.ind].bIfLoop:
                                          self.automaton[self.ind].loop()
                                      self.ind += 1
                                  self.ind = 0
                              else: break;
                  
                  start = time.time()
                  
                  evl = PEventLoop()
                  
                  PAirplane("Blog", 1, evl)
                  PAirplane("News", 2, evl)
                  PAirplane("Forum1", 3, evl)
                  PAirplane("Forum2", 3, evl)
                  PAirplane("Forum3", 3, evl)
                  PAirplane("Forum4", 3, evl)
                  
                  evl.run_until_complete()
                  
                  print("{:.2F}".format(time.time() - start))
                  exit()
                  


                  Здесь уже учтено:
                  1. Заключительное состояние одно и номер его «тьфу-тьфу» — 666
                  2. Введен класс PEventLoop, содержащий все что нужно для управления процессами-автоматами, т.е. список автоматов — automaton и их индексация — ind.
                  3. Цикл работает до тех пор пока не отработают ВСЕ процессы (раньше рубилось по первому закончившему работу)
                  4. Печать времени вынес за цикл (а то, действительно, неудобно как-то ;))
                  Немного напрягает меня передача ссылки на объект PEventLoop, но пока так.
                  Работает так, как надо — параллельно. В последнем приведенном примере при увеличении числа процессов (а времена у них замечу тоже разные) время их работы не увеличивается. Класс!
                  Что значит «блок схема генератора»? А какая блок схема у автомата? Абстрактного автомата.

                  Я сказал блок-схема? :( Я попросил граф автомата. Автомат имеет несколько типовых способов описания — граф, таблица переходов, матричное описание… Блок-схемы среди них НЕТ. Я рисую обычно граф, как наиболее понятную и известную форму (см. графы в статье).
                  Если, как Вы утверждаете, генератор это автомат, то «пли-з-з-з» его граф. Он его должен иметь по определению. Как-то так… :)
                  А Вам еще раз спасибо! Вы меня стимулируете. Так, глядишь, и питон освою хоть немного;)
                  А то все тут некоторые — «минусы-минусы-минусы» Они ж мне, как слону дробина! :) Тьюфу на них (и на минусы, кстати, тоже)!:) Не интересно :(
                    0

                    Говоря про "абстрактный" автомат, я типа намекнул, что неплохо бы добавить конкретики. Какого именно генератора? Что он должен делать? А не просто "граф генератора", как будто у любого генератора и автомата есть граф по умолчанию.
                    Я могу показать вам код sleep в виде генератора. Думается мне что граф у него точно такой же как у вашего PSleep, зато читается и понимается в разы проще, и кода у него сильно меньше:


                    import time
                    
                    def sleep(secs):
                        end = time.time() + secs
                        while time.time() < end:
                            yield
                    
                    # Имитация примитивного евент-лупа (без евентов)
                    queue = [sleep(10), sleep(5)]
                    while queue:
                        iterator = queue.pop(0)
                        try:
                            # То же самое, что вызов метода .loop() у ваших автоматов
                            next(iterator)
                            # Возвращаем "корутину" в конец очереди
                            sleeps.append(iterator)  
                        except StopIteration:
                            # "Автомат" завершил свою работу, не возвращаем его в очередь
                            pass

                    А вот так он будет выглядеть если его сделать без "сахара":


                    class Sleep:
                        def __init__(self, secs):
                            self._end = time.time() + secs
                    
                        def __next__(self):
                            if time.time() >= self._end:
                                raise StopIteration
                    
                    queue = [Sleep(10), Sleep(5)]
                    ...

                    Согласитесь — это практически тоже самое, что ваш PSleep. Можете даже вынести проверку условия в отдельный метод и назвать его x1(), если вы пишете код лично для себя.

                      0

                      Опечатка в коде. Вместо
                      sleeps.append(iterator)
                      надо
                      queue.append(iterator)

                        0
                        Так, да не так ;) Мне кажется, что Вы немного слукавили… Интересно посмотреть на полное решение. Объясню почему. Больше всего времени мне пришлось потратить не на сами автоматы, а на реализацию вложенной работы автоматов. А как здесь это будет выглядеть? Здесь по аналогии это надо, наверное, назвать вложенными корутинами. Т.е. в нашем случае мы имеем параллельную работу пауков. В моем случае — автоматов, в Вашем — корутин. Отдельный автомат-паук вызывает задержку-автомат, т.е. вкладывает в себя, и пока она работает — стоит. Задержка отрабатывает, удаляется, — автомат продолжает работу. Вот как-то так.
                        Просто по отдельному процессу-задержке судить сложно да и, думаю, нельзя.
                        У автомата есть состояния. Они имеют конкретные имена. В каком состоянии находится процесс можно подсмотреть со стороны и засинхронизироваться с ним. Как с этим?
                        У автомата есть дискретное время, которым можно управлять, замедляя, ускоряя процесс. Здесь как? И речь даже не о времени отдельного автомата, а о дискретном времени их множества, т.е. о сети автоматов…
                        Вот что вызывает вопросы, когда говорим об автоматах…
                          0

                          Как я понял, у вас автоматы напрямую взаимодействуют с неким внешним "планировщиком". Как минимум добавляя в него новые автоматы, результат которых им нужен.
                          В случае с корутинами предполагается, что сами они не работают напрямую с евент-лупом, а просто возвращают некий future-like объект (или promise). И евент-луп не вернёт управление исходной корутине пока не будет завершено выполнение этой future (а если точнее, то возврат управления — это просто callback прописанный внутри future). Future — это по сути "состояние автомата", которое имеет как минимум три значения: выполняется, завершено, ошибка. Переход из одного состояния в другой выполняется путём "внешнего воздействия" на future. И в него можно добавить callback-и на завершение и ошибку. По моему это примерно тоже самое, что у вас делается с помощью pFAwaitSleep->FCall().
                          И мне почему-то кажется, что future — это результат, который получился из попыток унифицировать и упростить работу с автоматами в том виде как это реализовано у вас. Евент-луп работает с future-объектами, а что там за ними стоит, какие калбяки они вызывают — это не его забота.


                          Фактически вся внутрянка, на которой работает асинхронный код — это всё автоматы. А за счёт специальных языковых возможностей (async/await), у программиста есть возможность писать короткий, легко читаемый код в "синхронном" стиле. В принципе можно писать код без async/await, и явно оперировать с Future-ами. Но это не удобно и порождает сильно вложенный код и callback-hell.


                          Согласен, что возможность отследить состояние автомата в явном виде — это удобно в некоторых случаях. При желании можно и в корутинах отслеживать статус, но он не будет обязательным для работы самой корутины, а просто как некая мета-дата. Но обычно в этом случае используют "примитивы синхронизации" (которые, наверное, тоже автоматы). Они позволяют синхронизировать разные "задачи" без использования "магических констант" и нарушения инкапсуляции, когда код снаружи "автомата" почему-то знает о его внутренних статусах.


                          PS: На самом деле меня не коробят ваши автоматы в C++. Пишите на нём как вам удобно. Больше всего меня задело, то что вы не зная Python пишите на нём совершенно некорректный код. А потом ещё и делаете какие-то выводы на его основе, и даже местами сравниваете с C++. Не надо так. Пишите на C++, и мне будет всё равно. Я наверное даже читать не буду, т.к. я давно на нём не пишу и не планирую в будщем.

                            0
                            Фактически вся внутрянка… — это всё автоматы
                            В этом есть 100%-я уверенность, что это не так. Иначе это как-то вылезло бы в наружу и уж я бы их распознал.

                            А за счёт… есть возможность писать короткий, легко читаемый код в «синхронном» стиле.
                            Я не буду спорить, что он читается легче. Когда-то и сам так писал, да и сейчас методы автомата пишутся в этом же стиле. Это норм. А вот на уровне самого алгоритма этот «синхронный» стиль меня не устраивает. В двух словах не скажешь. На эту тему все мои предыдущие статьи.
                            Согласен, что возможность отследить состояние автомата в явном виде — это удобно в некоторых случаях.
                            В автоматном программировании (АП) это элементы естественного состояния программы. И потребоваться могут в любой момент. Мне не надо даже задумываться — беру и пользуюсь. А все эти «примитивы» это те же «костыли», о которых я пишу. Вот с чем не хочется связываться.
                            PS: На самом деле меня не коробят ваши автоматы в C++.
                            Главное, что они не коробят меня :)
                            … не зная Python пишите на нём совершенно некорректный код.
                            Ну, не знаю — узнаю. Уже сейчас я больше знаю, чем пару дней назад :) По поводу некорректности. Если питон не выдает ошибок, то в чем тогда некорректность? В корявости? Дело наживное. Последний код намного приличнее, чем первый из статьи. Еще можно что-то скрыть через наследование, как сделано в С++. Но есть ли оно у объектов питона? Думаю в Вашей, с чьей-то помощью или сам, но разберусь. Если нет (наследования), то по мне и так сойдет. Питон пока не мой основной язык. Но бросать его пока не планирую. Жизнь устаканит… Тут вот она еще один язык подсовывает :( Нежданно… Освоим… В дополнение к С++ и питону;)
            0
            В ваших статьях бывает, что поднимаются интересные проблемы, но ваш код читать очень сложно вне IDE. Когда я был маленький, меня били за переменные с незначащими именами, особенно однобуквенные. Ну и гоняться за скоростью на питоне — это какое-то неочевидное решение :)
              –1
              Когда я был маленький, меня били за переменные с незначащими именами
              Ну, за такое, может, и надо. Но только надо знать, конечно, меру… :)
              У меня нет однобуквенных имен переменных. Если речь об «иксах» и «игреках», то, во первых, они пронумерованы. Во-вторых, имеют следующий вполне конкретный смысл. У автомата есть множество входных каналов и выходных. Входные — иксы, выходные — игреки. Каждый их номер — это номер соответствующего канала. На структурном уровне — это также «черный ящик» с множеством каналов…
              Когда-то я тоже хотел подобные имена сделать более одушевленными. Но потом отказался от этой идеи. Это было сделано несколько вынужденно, но теперь уверен, что так оно даже лучше. Мне, как минимум. Может, потому, что маленьким меня за подобное не наказывали ;)
              А за оценку идей — спасибо!
                +1

                А если каналов будет 20? А если 50? И вы впервые видите этот код, а вам надо оперативно что-то исправить. А представитель заказчика (как назло) объясняет свою проблему обычными словами, а не блок-схемами на которых указаны правильные иксы и игреки. А блок-схему для этого кода почему-то не нашли (наверное автор её забрал с собой, когда увольнялся).
                И конечно же автомат достаточно сложный. Его код минимуму на 3 экрана растягивается по высоте. Одним взглядом сразу не окинуть, и не увидеть что метод y25() вызывается не по тому условию. Там почему-то используется x23(), а надо x24() (наверное дети какие-то писали, которые не знают прописные истины).
                Но это я слишком сильно далеко заглянул в будущее. Вы в этом время скорее всего ещё только запускаете дебагер, что бы по шагам разобраться в том как работает этот автомат, и у вас впереди есть один или даже два интересных дня, что бы это сделать. Что-бы заново нарисовать блок-схему и уже в ней найти ошибку, т.к. просто смотря на код её не увидеть.

                  –1
                  А если каналов будет 20? А если 50?
                  Зашито до 32-х. Дальше — паника! :) Но вот сколько использую, но этого числа — за глаза! По входу обычно до 7-10, по выходу может максимум раза в два-три раза больше…
                  Одним взглядом сразу не окинуть
                  Логика — тьфу — это таблица переходов. А это десять плюс-минус строк. Рисуем — пять сек. Методы, как правило, небольшие. Классы, как видите, как правило, тоже не очень большие. Отлаживаются — на раз. Дальше только используй, т.к. все «заковано» в библиотеку отлаженных и проверенных не раз процессов. Отладка — все просто и понятно, как ясный день. Потоков — нет (тьфу-тьфу не накаркать бы)! Красота!
                  Короче — автоматная технология проектирования «панимашь»! Все отточено и выверено годами жесткой практики :)
                    +1

                    Хорошо, когда всё "обычно" и не выходит за рамки того "как правило". Но в проектах, над которыми работают несколько человек, которые при этом периодически меняются, лучше на это не надеяться. Кто-нибудь обязательно запилит автомат, который упрётся в лимит "32", и засунет в него всю бизнес-логику приложения. И будет он занимать 1000+ строк.
                    Хотя это конечно ваше дело, видимо вы или совсем не работали в команде. Или делали небольшие приложения, которые вы лично и поддерживали, и вам не приходилось каждые полгода объяснять новым членам команды, что означают все эти иксы и игреки. И в каком именно игреке надо искать код, который нужно поправить для решения задачи. Можно даже найти плюс в таком подходе, не надо ломать голову над одной из сложнейших задач программирования — придумывание названий.
                    Для меня же ваш код выглядит как результат работы обфуксатора или минимайзера. Там примерно такие же названия функций из 1-2 символов.

                      –1
                      Работал и один, и в команде, и руководил командами. Мозги у программеров сначала кипят, но затем модель быстро воспринимается и работается легко и просто. Для этого даже стандарт на автоматное проектирование был создан. Есть даже ГОСТ чем-то весьма похожий на него — P-схемы программ. От него, кстати, и отталкивались…
                      Кто-то просто так не запилит. Если упрется, то это или исключительный случай или чел просто тупит и не понимает как процесс разбить на множество небольших автоматов. Дальше так: не может — научим, не хочет — заставим. Если конечно не безнадежный случай ;)
                      Вот даже Вы сказали — сложнейшая задача. Здесь она упрощается. Конечно, нужно знать, что значит каждый вход/выход автомата. Но это загоняется не в имя предиката/действия, смысл которого чуть погодя совсем забывается, а в осмысленный комментарий к нему и в правильное документирование программы, которое в случае автоматов более осмысленно, чем для блок-схем.
                      Есть плюсы и минусы в отделении логики от текста предикатов. Но плюсов больше. Особенно, когда надо разобраться/вспомнить как оно/она работает ;)
                      О! Чуть не забыл. Кроме питона есть и MATLAB, а в нем — Stateflow — автоматное проектирование. Признано многими и крутым и весьма эффективным. Используется по всему миру и, думаю, в проектах любой сложности.
                  +1
                  Вот читаю листинг «Читаем и пьем чай асинхронно на Python» — вот зачем явные проверки state (причём сравнение с магическими числами) и вызовы нумерованных методов? Почему не задать таблицей, которая и породит экземпляр класса с нужными методами? Питон такие вещи позволяет, и это на фоне общей производительности интерпретатора, не очень дорого по памяти и времени.
                    –1
                    Это типичный подход к реализации/имитации логики автомата. А нумерованные методы? Я уже пояснил. Я не ломаю голову над его названием, а поясняю в комментах (если, конечно, есть время и желание :)) к предикату/действию. Но в целом отделение логики от содержимого методов удобнее для понимания программы. Мне — точно.
                    Ну а таблица? Надо посмотреть. В С++ у меня таблица переходов. Это очень удобно и по большому счету правильнее, т.к. в точности соответствует таблице переходов автомата.
                      0
                      Гляньте pytransitions, там просто дёргаем по имени перехода. Можно назвать все переходы одинаково. например, next, тогда в цикле дергаем next. Асинхронные вызовы поддерживаются.
                –1

                Что-то мало минусов поставлено статье на мой взгляд.

                  0
                  Что-то мало минусов

                  А на мой взгляд, так «шрамы только украшают мужчину»! :D
                  0
                  У меня два вопроса. Если сформулировать их мягко, то 1) какую проблему пытался решить автор 2) зачем ему для решения понадобился python, если пишет он на C++
                    0
                    Автор отвечает… :)
                    1) Меня категорически не устраивают существующие технологии программирования. У меня автоматное параллельное мышление. По-другому уже не то, что не могу, — не хочу. Вот такой я «иносранец».
                    2) Шайтан попутал :) Смотрю народ от корутин тащится, которые (сопрограммы) я отринул много лет тому назад. И чего они в них нашли, думаю? ;) Вот так на питон и вынесло. Поначалу, чуть было не зацепило за Котлин, но… не сложилось как-то.

                    А С++ ни куда не денется, видимо. Теперь хоть немного в теме, что творится помимо него. А то уж совсем было скучно стало… Чтобы совсем не закиснуть решил задокументировать свои идеи и народ повеселить ;) Правда, тут уже наклевывается веселуха, т.к. что «горшочек», похоже, будет варить уже не так активно. Накликали, похоже… ;)
                      0
                      Корутины можно и на чистом С изобразить, через Duff's device, явный или неявный.
                        0
                        На чистый С не реагирую в принципе после того, как увлекся объектами, т.е. С++.
                        И Вы думаете, что меня будут интересовать корутины в С, если есть у меня автоматы и С++? Только как экзотика. ;)
                          0
                          Объекты сильно переоценивают :)
                            0
                            Скорее, кто их не использует — сильно недооценивает ;) Это просто уже другое мышление. А если еще объекты активные, то совсем красота!
                        0
                        Положим и я, зная python, совсем не в восторге от asyncio. Но отторжения явного не вызывает, хотя модель конкарренси в голанг мне куда больше нравится
                          0
                          Речь даже не о языке, а о сути, которую он выражает и реализует. Корутины ограничены в своих возможностей вне зависимости от того, на каком языке они реализованы. Это не вычислительная модель, как те же потоки. Здесь можно со мной спорить, но есть теория, а она на стороне автоматов.

                    Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

                    Самое читаемое