Подготавливая статью [1] к публикации, обратил внимание на картинку, показанную на рис. 1. Я сохранил ее, чтобы воспользоваться в будущем. И оно не заставило себя ждать, т.к. захотелось повысить наглядность решения, введя в него графику и используя именно эту картинку.  К чему это привело, далее мы и поговорим.

Все, что связано с картинкой, сделать не так уж сложно. Это довольно подробно описано в цикле статей по реализации графики в ВКПа (см. [2]). Для этого, во-первых, нужно создать графическое окно, установив данную картинку в качестве фона. Во-вторых, воспользоваться существующими заготовками контролов (элементов графического интерфейса), которые необходимо будет разместить на данном фоне.

Рис.1. Задача об обедающих философов
Рис.1. Задача об обедающих философов

План действий

Первое. Создаем графическое окно (далее просто окно). Все нужное есть. За его создание отвечает процесс FViewControls графической библиотеки среды ВКПа fsagdip. Как это сделать, подробно описано в [2].

Второе. Создаем «философов» и распределяем их вокруг стола (как на рис. 1). Их образами будут процессы типа FControl  все той же библиотеки fsagdip. Далее необходимо создать сами графические образы, представляющие состояния философа.  В нашем случае ими будут четыре файла типа png (по одному каждого цвета) с прозрачным фоном. Необходим также процесс управления ими. Вот его нужно создать. Но это тоже несложно .

Третье. Модель философа должна иметь состояния, которые перечислены на рис. 1 (см. прямоугольники). В исходной модели их фактически два (см. модель философа в [1]): в состоянии «wf» (да и «wo») философ размышляет и это соответствует состоянию «Философ думает» на рис. 1. В состоянии «eo» он готовится приступить к еде, что логично сопоставить состоянию «Философ голоден».

У нас нет состояний «Философ ест» и «Философ насытился». Исходя из их смысла, они должны между состояниями «eo» в «wf». Сейчас на этом переходе философ сразу «ест». Как их включить в существующую модель мы также опишем ниже.

Расширяем модель философа

Вариантов совершенствования модели философа множество. Мы пойдем естественным путем. Сначала воспользуемся объектным характером автоматной модели. Новая модель будет дочерним объектом существующего автоматного класса. В нее мы добавим два недостающих состояния. В них модель будет «притормаживать, чтобы внешний процесс смог на это отреагировать и отобразить их соответствующим цветом в окне.

Новая модел�� философа приведена на рис. 2. Добавленные в автомат элементы выделены красным цветом. Переходы между новыми состояниями создают задержки, на время которых модель будет «замирать» в текущем состоянии. 

Рис. 2. Новая модель философа
Рис. 2. Новая модель философа

Задержки представляют собой вложенные автоматы. Когда модель их создает, то для внешнего наблюдателя она остается в состоянии, которое породило вложенный автомат. И только по завершению работы вложенного автомата текущим состоянием становится то состояние, на которое указывает создавший задержку переход (подробно вложенные автоматы рассмотрены в [3]).

Код нового философа на С++ - его заголовочный файл и реализация класса,  показаны в листинге 1.

Листинг 1. Код философа на С++й текст
class FDppColor :
    public FDppPhilFork
{
public:
    LFsaAppl* Create(CVarFSA *pCVF) { Q_UNUSED(pCVF)return new FDppColor(nameFsa, pCVarFsaLibrary); }
    bool FCreationOfLinksForVariables();
    FDppColor(string strNam, CVarFsaLibrary *pCVFL);
    virtual ~FDppColor(void);
    CVar    *pVarMealTime{nullptr};         // время еды
    CVar    *pVarHighTime{nullptr};         // время отдыха после еды
protected:
    int x6();
    int x8();
    void y7(); void y8();
};

#include "stdafx.h"
#include "FDppColor.h"
#include <QRandomGenerator>

static LArc TBL_DppColor[] = {
    LArc("st",		"st","^x12",	"y12"),			//
    LArc("st",		"wf","x7x12",	"--"),			//
    LArc("wf",		"eo","x6",      "--"),			//
    LArc("wf",		"wo","x3^x6",	"y3"),			//
    LArc("eo",		"eats","x2^x4",   "y1"),		//
    LArc("eo",		"wf","x4",      "y4"),			//
    LArc("eo",		"eo","^x2^x4",  "y5"),			//
    LArc("wo",		"wf","^x3",     "--"),			//
//    LArc("wo",		"wf","x8",     "--"),			//
    LArc("eats",	"high","--",    "y7"),			//
    LArc("high",	"wf",   "--",   "y8"),			//
    LArc()
};

FDppColor::FDppColor(string strNam, CVarFsaLibrary *pCVFL):
    FDppPhilFork(strNam, pCVFL, TBL_DppColor)
{ }

FDppColor::~FDppColor(void)
{ }

bool FDppColor::FCreationOfLinksForVariables() {
    pVarMealTime = CreateLocVar("nMealTime", CLocVar::vtInteger, "meal time", true, "2000");
    pVarHighTime = CreateLocVar("nHighTime", CLocVar::vtInteger, "high time", true, "3000");
    FDppPhilFork::FCreationOfLinksForVariables();
    return true;
}

int FDppColor::x6() {
    bool b = string(pFDppPhil_Left->FGetState()) == "wf";
    if (!b) return false;
    return FDppPhilFork::x6();
}

int FDppColor::x8() {
    bool b = string(pFDppPhil_Left->FGetState()) == "high";
    return b;
}


void FDppColor::y7() {
    int n = pVarMealTime->GetDataSrc();
    int nRnd = QRandomGenerator::global()->bounded(n/2, n);
    this->FCreateDelay(nRnd);
}

void FDppColor::y8() {
    int n = pVarHighTime->GetDataSrc();
    int nRnd = QRandomGenerator::global()->bounded(n/2, n);
    this->FCreateDelay(nRnd);
}

В новую модель философа мы добавили указатели на переменные, которые будут задавать значение задер��ек. Сами переменные создаются методом FCreationOfLinksForVariables(),  а изменять их можно, используя диалоги управления переменными среды ВКПа. Как узнать их значения в коде, демонстрируют методы y7 и y8.  При этом, чтобы сделать работу модели более динамичной, мы будем их изменять в небольших пределах случайным образом.

В модель добавлены предикаты – x6, x8 для анализа свойств соседних процессов текущей модели. Хотя новым является только предикат x8, а x6 – это подправленная версия родительского предиката.

Проблемы модели

При тестировании конфигурации почти сразу проявила себя проблема, которую в графической форме представляет рис. 3. Видно, что третий и четвертый философ одновременно поглощают пищу. Этого не должно быть, поскольку нарушено  условие единоличного владения общим ресурсом - вилкой.

Рис. 3. Демонстрация проблемы доступа к общему ресурсу
Рис. 3. Демонстрация проблемы доступа к общему ресурсу

Почему это произошло (далее нужно быть внимательным!)? 

Текущий философ под номером 4 , которому разрешили приступить к еде, покинув состояние «wf», перешел в «eo», изготовившись к еде (см. рис. 2). На следующем такте, сосед справа (философ 3), который размышляет и еще не хочет кушать, переходит в состояние «wo». Так он разрешает 4-му взять свою вилку. Это ведет к тому, что на следующем такте 4-й приступает к еде, перейдя в «eats», а 3-й при этом возвращается в «wf», т.к. его сосед слева, т.е. 4-й, не находится в состоянии «eo» (x3=false) и на его взгляд уже поел. Далее - внимание! -  3-му разрешается приступить к еде (его сосед справа – 2-й все еще размышляет, т.к. не нужна сортировка вилок), но при этом его сосед слева, т.е. 4-й, все еще ест. В результате 3-й, считая, что его вилки свободны, направляется в «eo» (при этом 2-й  должен быть в «wo»). Но его сосед слева - 4-й все еще ест или, как минимум, находится в состоянии «сытости» - «high», а потому вилка все еще занята! Однако, 3-й переходит в состояние «eats» и мы видим, что оба философа становятся синего цвета или, в крайнем случае, 4-й будет белым, а 3-й синим. Налицо конфликт доступа к общему ресурсу. И все из-за ошибки 3-го философа при выборе своих действий.

Одно из решений – заменить на переходе wo->wf предикат x3. Для этого создадим предикат x8. Он примет истинное значение, если сосед слева в состоянии «high».  После замены предикатов, философ 3 (как, кстати, и любой другой) покинет состояние «wo» только тогда, когда философ 4 попадет в состояние насыщения. Правда, более надежным решением было бы взять состояние «wf». В этом случае 4-й уж точно освободит вилку.

Другое решение – изменить предикат родительского класса x6. В исходном виде он анализирует состояние соседа справа и необходимость сортировки. Добавим в него анализ соседа слева. В этом случае не нужно: 1) изменять предикат x3, 2) добавлять предикат – x8 и 3) изменять поведение модели. Код на листинге 1 соответствует именно такому решению. Успешно решенную проблему отражает рис. 4.

Рис. 4. Устранение проблемы владения общим ресурсом
Рис. 4. Устранение проблемы владения общим ресурсом

Работу приложения дополнительно демонстрирует «гифка» Gif 1.

Gif 1.
Gif 1.

Графика философов

Код управления контролами, о котором мы говорили в начале разговора, приведен в листинге 2. Он может показаться сложным, но, поверьте, он несравненно проще того, который породил DeepSeek. Во-первых, здесь чуть более 160-ти строк кода. Во-вторых, большая часть – код, типичный для любого автоматного класса на С++ в ВКПа. А потому создание его - буквально минутное дело (по хлопку).

Для сравнения: на графику для ESP32 мы с DeepSek потратили почти два дня (наплясались – знатно!). Наверное, это показатель нашей квалификации, но, тем не менее. Ни чего с собой не могу поделать, но для меня «птичий язык» HTML намного хуже воспринимается, чем код на С++… А там, ведь, еще скрипты есть! – прямо ужас какой-то!

Листинг 2. Код процесса управления контролом философа
#include "lfsaappl.h"

class FControl;
class FDppColor;
class FControlPhil :
    public LFsaAppl
{
public:
    LFsaAppl* Create(CVarFSA *pCVF) { Q_UNUSED(pCVF)return new FControlPhil(nameFsa, pCVarFsaLibrary); }
    bool FCreationOfLinksForVariables();
    FControlPhil(string strNam, CVarFsaLibrary *pCVFL);
    virtual ~FControlPhil(void);
    CVar *pVarScaleX, *pVarScaleY, *pVarNot, *pVarWork{nullptr};
    CVar    *pVarStrNamePhil{nullptr};         // имя философа слева

    string strNameWork{""};
    string strNameControl{""};
    FControl *pFControl{nullptr};
    int nX;
    int nY;
    double dScaleX;
    double dScaleY;
    FDppColor  *pFDppColor;
    string strSaveState{""};
protected:
    int x1(); int x2(); int x3(); int x12();
    void y1(); void y3(); void y4(); void y12();
    bool	bNot;
friend class CDlgControlPhil;
};

#include "stdafx.h"
#include "FControlPhil.h"
#include "FControl.h"
#include "FViewControls.h"
#include "ChildViewControls.h"
#include "FDppColor.h"

static LArc TBL_ControlPhil[] = {
    LArc("st",		"st","^x12",	"y12"),			//
    LArc("st",		"ss1","x12x3",	"y3"),		//
    LArc("ss1",		"s1","--",      "y4"),			//
    LArc("s1",		"s1","--",      "y1"),			//
    LArc()
};

FControlPhil::FControlPhil(string strNam, CVarFsaLibrary *pCVFL):
    LFsaAppl(TBL_ControlPhil, strNam, nullptr, pCVFL)
{
}

FControlPhil::~FControlPhil(void)
{
}

bool FControlPhil::FCreationOfLinksForVariables() {

    pVarStrNamePhil = CreateLocVar("strNamePhil", CLocVar::vtString, "Name Phil");
    string strNamePhil = pVarStrNamePhil->strGetDataSrc();
    pFDppColor = static_cast<FDppColor*>(pTAppCore->pSetVarFSA->GetAddressFsa(strNamePhil));
    if (pFDppColor == nullptr) return false;
    pVarNot = CreateLocVar("bNot", CLocVar::vtBool, "negation operation");
    strNameControl = pVarPrmProc->strParam1;
    pFControl = static_cast<FControl*>(pTAppCore->pSetVarFSA->GetAddressFsa(strNameControl));
    if (pFControl == nullptr) return false;

    strNameWork = pVarPrmProc->strParam6;
    pVarWork = pTAppCore->GetAddressVar(strNameWork.c_str(), this);

    if (pFControl)	{
        nX = atoi(pVarPrmProc->strParam2.c_str());
        pFControl->SetX(nX);
        nY = atoi(pVarPrmProc->strParam3.c_str());
        pFControl->SetY(nY);
        dScaleX = atof(pVarPrmProc->strParam4.c_str());
        pFControl->SetScaleX(dScaleX);
        dScaleY = atof(pVarPrmProc->strParam5.c_str());
        pFControl->SetScaleY(dScaleY);
        static_cast<FControl*>(pFControl)->pFMainAppl = this;
    }
    if (pVarPrmProc->strParam14 != "") {
        pVarNot->SetDataSrc(this,atoi(pVarPrmProc->strParam14.c_str()));
        bNot = atoi(pVarPrmProc->strParam14.c_str());
    }
    strSaveState = "";
    return true;
}

int FControlPhil::x1() {
    if (bool(pVarNot->GetDataSrc()))
        return !bool(pVarWork->GetDataSrc());
    return
        int(pVarWork->GetDataSrc());
}
//	есть указатель на образ, есть переменные имени образа, переменной работы?
int FControlPhil::x2() {
    string str = FGetNameFsa();
    if (pFControl == nullptr)
        return false;
    return pFControl->pVarBMP && pVarWork;
}
//	состояние образа "s1"?
int FControlPhil::x3() {
    if (!static_cast<FViewControls*>(static_cast<FControl*>(pFControl)->pFViewControls)) return false;
    if (!static_cast<FViewControls*>(static_cast<FControl*>(pFControl)->pFViewControls)->pCChildViewControls) return false;
    return pFControl->FGetState() == "s1";
}

int FControlPhil::x12() {
    if (pFControl == nullptr) return false;
    return pFControl->pVarBMP && pVarWork && pFDppColor;
}

void FControlPhil::y1() {
    string strState = pFDppColor->FGetState();
    if (strSaveState == strState) return;
    if (strState == "eats") {
        string str = static_cast<FControl*>(pFControl)->strNamePathBMP + "\\Blue.png";
        pFControl->pVarBMP->SetDataSrc(this, str, this);
        strSaveState = strState;
    }
    if (strState == "wf") {
        string str = static_cast<FControl*>(pFControl)->strNamePathBMP + "\\Yellow.png";
        pFControl->pVarBMP->SetDataSrc(this, str, this);
        strSaveState = strState;
    }
    if (strState == "eo") {
        string str = static_cast<FControl*>(pFControl)->strNamePathBMP + "\\Red.png";
        pFControl->pVarBMP->SetDataSrc(this, str, this);
        strSaveState = strState;
    }
    if (strState == "high") {
        string str = static_cast<FControl*>(pFControl)->strNamePathBMP + "\\White.png";
        pFControl->pVarBMP->SetDataSrc(this, str, this);
        strSaveState = strState;
    }
    nX = atoi(pVarPrmProc->strParam2.c_str());
    pFControl->SetX(nX);
    nY = atoi(pVarPrmProc->strParam3.c_str());
    pFControl->SetY(nY);
}
//
void FControlPhil::y3() {
    QWidget *pW = pFControl->pFViewControls->pCChildViewControls;
    static_cast<FControl*>(pFControl)->pWgt = new QFrame(pW);                              // создание виджета
    static_cast<FControl*>(pFControl)->pWgt->setAutoFillBackground(true);
}
//
void FControlPhil::y4() {
    string str = static_cast<FControl*>(pFControl)->strNamePathBMP + "\\Face.png";
    pFControl->pVarBMP->SetDataSrc(this, str, this);
}

void FControlPhil::y12() { FInit(); }

Выше действия y3 и y4 создают виджет «философа», а действие y1 в цикле приводит его вид в соответствии с текущим состоянием. Обратите внимание, что оно запускается ровно тогда, когда текущее состояние изменяется.

Миграция философов на ESP32

На микроконтроллере (МК) ESP32 есть все для быстрой реализации философов (см. также [4]). Для этого достаточно фактически один в один перенести их из Qt в среду VSCode. Правда, речь пока идет о  коде без графики. Это, конечно, немного огорчает, но графическая часть ВКПа пока недостижима для ESP32 просто в силу недостатка ресурсов. Тем не менее, как мы увидим, выход есть и здесь.

Итак, по хлопку и в два притопа философы переместились на ESP32.  Код их на С++ чуть позже будет выложен в свободный доступ на Git в разделе examples. Решение данной задачи, правда,  потребовало «допилить» VCPa, а потому будет и обновленная версия  ядра VCPa. Задуманный перенос прошел быстро, безошибочно, т.е. вполне себе безболезненно.  

А как же графика? После Qt без нее решение  уже не смотрится. Пришлось привлечь DeepSeek, т.к. в одиночку подобные «подвиги» мне нынче не по силам. Да и зачем заниматься тем, с че�� лучше (ничего, если даже и хуже) справятся другие? Не так ли?

В конце концов, совместными усилиями графику мы все же осилили (см. Gif 2). Теперь, используя WiFi, вы можете открыть Web-страничку и наблюдать «битву» за макароны. Внешне получилось даже красивее, чем в Qt. Но, правда, это более 900 строк кода на HTML (только веб-страница). Для сравнения в ВКПа все философы в трех вариантах занимают примерно 1050 строк кода на С++. 

Gif 2.
Gif 2.

Код философов с графикой для ESP32, правда, в ограниченном доступе, будет, скорее всего, тоже выложен. В нем будет «все в одном флаконе». А именно,  ставим среду VSCode, загружаем проект, подключаем МК к USB и при наличии WiFi любуемся философами в графическом виде.

Подведение итогов

Что мы получили кроме красоты? Повысили наглядность, что сразу же высветило проблему.  А вовремя обнаруженная и локализованная проблема позволяет, как вы понимаете, избежать множества неприятностей в будущем и сэкономит массу ваших денежек. 

Реализация демонстрирует преимущества объектной модели программирования. Привет тем, кто не признает и/или ограниченно использует ООП (а за отдельных любителей Rust-а я даже переживаю)! Лично я давно перестал воспринимать серьезно языки программирования без ООП. Я, конечно, не против них, но их противник. Пусть они будут проблемой тех, кто является их поклонниками.

Мы легонько так изменили поведение модели и ввели в нее дополнительные действия.  Этому весьма способствовало выделение логики поведения в объекте в отдельную структуру. Это таблица переходов (ТП) автоматной модели философа. В обычном программировании операторы управления - это винегрет с остальными операторами. Это затрудняет понимание логики программы, затрудняет ее модификацию, документирование.

И уж совсем легко – перенос философов на ESP32. Там они себя чувствуют, если не лучше, чем в Qt. Графика, правда, - больная тема. Но тут в помощь нам DeepSeek. Главное, ему толково объяснить, что нам нужно. Хотя… Ну, помог и помог и на том спасибо.

 Вот так - в два притопа, три прихлопа и … с плясками с бубном с DeepSeek мы не дали перессориться и помереть с голоду философам.  На С++. А вот как они себя чувствуют на Go – это надо разбираться. Я так за них переживаю! Но пока вестей от них – ни гу-гу. Как и от «оптимистов» с «пессимистами», использующих многопоточность (подробнее см. [5]). Ну, да ладно – это, с одной стороны, их проблемы.  Но вот философов - жаль. Но статья, кстати, прекрасная. Как пример.  Чтобы после ее прочтения я использовал многопоточность (сами потоки – это другое)!? Да вы что, прошу прощения, «с дуба рухнули»?!  Тем более, что теперь уже даже формально доказано (см. [1]), что многопоточность – это тяжелый пережиток нашего славного прошлого.

Литература

1.       Искусство выжить. Простое руководство для настоящих программистов. https://habr.com/ru/articles/969036/

2.       ВКПа. Введение, ч.3. Графика. Имитационное моделирование. https://habr.com/ru/articles/797179/

3.       Автоматное программирование: определение, модель, реализация. https://habr.com/ru/articles/682422/

4.       Как избежать кошмара параллелизма в IoT: автоматы вместо потоков и корутин. https://habr.com/ru/articles/937448/

5.       Три способа менять один объект из нескольких потоков. Больше нет. https://habr.com/ru/articles/974198/

Комментарии (4)


  1. itGuevara
    10.12.2025 07:04

    Нельзя ли задачу формализовать какой-либо нотацией workflow (события и операции)? И потом по ней делать расчеты - вычислять положение маркера. Как вариант - более простую задачу рассмотреть.


    1. lws0954 Автор
      10.12.2025 07:04

      Если речь о философах, то все формализовано. Один философ - конечный автомат. Много философов - сеть автоматов. Зачем еще события? Что за вычисление положения маркера? Зачем что-то вычислять да еще и положение? Уточните, а то, прямо скажем, не очень понятно. И что понимается под "более простой задачей". Что хотелось бы Вам упростить в данном случае?


      1. itGuevara
        10.12.2025 07:04

        Если речь о философах, то все формализовано.

        Математикой. Как вариант, сетью Петри. Но хотелось бы и иные формализмы, позволяющие формулой рассчитать следующий переход (вероятностный) при известном текущем состоянии.


        1. lws0954 Автор
          10.12.2025 07:04

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

          Так что, если сравнивать модели, то сеть автоматов лучше сетей Петри. Просто потому, что она точно описывает поведение множества объектов, а сеть Петри, как правило, с какой-то вероятностью.

          Итак. Не нужны другие модели, т.к .используется модель сетей автоматов. Если хотите сравнить с другими моделями - тоже не проблем. Но нужны эквивалентные переходы от одной модели к другой. К примеру, в тех же сетях Петри есть как перейти от сети Петри к эквивалентному автомату (Питерсон).

          Если хотите "вычислять", то у автоматов тоже нет проблем - есть алгебра Автоматов. Может есть и алгебра сетей Петри, но что-то не слышал. Да и, если честно, не копал их так глубоко. Просто потому, что автоматы меня больше устраивают, чем Петри.