Это первая статья цикла, предусматривающего рассмотрение логических схем или, более общо, логических процессов. Будет создан базис из логических элементов, позволяющих собрать любую схему. Специалистам, погруженным в бизнес-процессы, такая тема может показаться не стоящей внимания. Но мне тоже не понятно, почему бизнес-процессы выделяют в отдельную категорию. С формальной точки зрения они ни чем не отличаются от любых других процессов.
Таким образом, если вас интересуют общие проблемы параллельных процессов, то в этой и в последующих статьях на примере логических процессов мы их и рассмотрим. Терминологически мы будем придерживаться словаря по вычислительной технике под редакцией В.Иллингоута[1]. Но это может быть учебная литература, подобная [2], научная литература, как монография [3], или научно-популярные книги типа [4, 5].
Литературы по автоматам много. Бум пришелся на 80-е годы прошлого века, а сейчас лишь отголоски прошлого. Поэтому, с одной стороны, такой информационный массив позволяет выбрать наиболее подходящую литературу, но, с другой стороны, орождает множество толкований модели автомата, среди которых разобраться не так уж просто.
У меня сформировался свой вариант модели конечного автомата (КА), который далее будет основным. Данная модель, во-первых, очень близка к классической форме. А это важно, т.к. позволяет использовать теорию почти без исключений. А, во-вторых, она удобна для практики программирования, допуская эффективную ее реализацию. Более детально все эти вопросы освещены в статье [6].
Я, надеюсь, не похож на «мазохиста», терпящего «пот, боль и кровь» многопоточности (см. обсуждение статьи [7]). Тем не менее, в планы входит создание элементов на базе потоков. Все ли получится еще не ясно, но есть надежда на вполне благополучный исход, исходя из опыта использования потоков в контексте автоматного программирования.
Модель элемента задержки
Начнем, пожалуй, с не самого простого - модели задержки. Она базовый элемент всех логических элементов, где прямо или косвенно присутствует везде. Это подтверждается также тем, что ни чего мгновенно в природе нет. Например, среди взаимодействующих процессов нет таких, которые не требовали бы ее учета. Кроме того, у задержки есть определенный «характер», которым наделяются и включающие ее процессы.
Приведем определение задержки из словаря В.Иллингоута. Оно следующее:
P.296 propagation delay
Время, необходимое для того чтобы изменение сигнала на входе логического вентиля (L.123 Logic gate) или логической схемы (L.116 logic circuit) вызвало изменение на их выходе. Обычно это очень короткий промежуток времени. Он различен для разных схем и вентилей и вызван неизбежными задержками на переключение транзисторов.
Автоматная модель логического элемента «задержки на распространение» (далее просто задержки) в форме модели конечного автомата показана на рис. 1.

![Рис.2. Примеры из книги Кудрявцев В.Б и др.[2] Рис.2. Примеры из книги Кудрявцев В.Б и др.[2]](https://habrastorage.org/r/w780/getpro/habr/upload_files/da7/e2c/ea0/da7e2cea049531705bc6383c14dd227f.png)
На рис. 2 приведены автоматы в классической форме, которая, судя по учебнику [2], является базовой в МГУ. Здесь задержка представлена таблицей на рис. 1.12 и графом на рис. 1.13 (рис. 1.3 пример параллельной системы). Эти модели автоматов достаточно легко конвертируются друг в друга, но автомат на рис. 1 включает то, что не учитывает модель на рис. 2. Например, вложение автоматов, как аналог понятия подпрограммы. Они в «классике» фактически не рассматриваются. Не отражается и тип задержки. Относительно абстрактной модели классического автомата можно сказать, что ее использование достаточно специфично, а потому более удобна модель структурного типа.
Но вернемся к задержке. Автомат на рис.1 из начального состояния «st» перейдет в состояние «s0» или «s1». Только произойдет это после того, как в рамках действия y12 будут инициированы ссылки на переменные модели и значение задержек. Выходная переменная устанавливаются действиями y1 и y2. В модели это действия автомата Мура, которые выполняются только один раз при входе в соответствующее состояние. Действия y3 и y4 (это уже автомат Мили) устанавливают текущее значение задержки, создавая вложенные автоматы, которые отсчитывают заданное число дискретных тактов.
Таким образом, модель на рис. 1 отслеживает текущее состояние входа и при его изменении переходит в то или иное состояние, устанавливая с задержкой выходной сигнал. Напомним, что сигналам автомата в этом случае соответствуют переменные программной модели задержки.
Транспортные и инерционные задержки
Задержки делятся на транспортные и инерционные. Транспортные всегда передают на выход изменения сигналов на входе, а инерционные лишь в случае, если длительность входного сигнала превышает значение задержки. Выше была рассмотрена задержка транспортного типа. Модель задержки, которая может быть как транспортной, так и инерционной представлена на рис. 3. На графе автомата все, что относится к инерционной задержке, выделено красным цветом.

Инерционная задержка при изменении входного сигнала переходит в состояние «0» или «1». Если при этом входной сигнал в течение времени задержки не возвращается к исходному, то его значение передается на выход. В противном случае задержка возвращается в состояние, предшествующее изменению входного сигнала, а выход остается в текущем состоянии.
При переходе в состояния «0» или «1» создается циклический параллельный процесс с циклом, равным значению задержки. Затем одновременно контролируется входной сигнал, и активность параллельного процесса (предикат x3). Задержка перейдет в противоположное состояние, если за время активности параллельного процесса значение входа не изменится. В противном случае она вернется к предыдущему состоянию. При этом запускается действие y5, уничтожающее параллельный процесс.
На рис. 4 приведены результаты тестирования задержек, одна из которых транспортная - средний график, а другая инерционная - верхний график. Видно, что инерционная задержка фильтрует короткие импульсы, а транспортная задержка реагирует на изменение сигнала, транслируя его на выход. Но, что важно, во время работы вложенного автомата транспортной задержки любое изменение входного сигнала ею игнорируется.

Однако транспортная задержка реагирует даже на очень короткий входной сигнал, который инерционная задержка отфильтрует. В определенном смысле она тоже фильтрует входной сигнал, но делает это лишь тогда, когда находится в режиме реализации задержки.
Итог
Окончательный итог подводить пока рано, т.к. еще есть о чем поговорить. Например, если все делать на автоматах, то при большом их числе ядро среды ВКПа будет перегружено. Это, кроме влияния на скорость процессов, снижает и качество управления.
Использование медленных автоматных пространств среды ВКПа снижает нагрузку на ядро, но, порой, не спасает и это. Может помочь использование таймеров и потоков. Подобные задержки (уж поскольку мы их рассматриваем) фактически уже созданы. Вот обо всем этом мы и поговорим в продолжение начатого разговора.
PS
Не удержался, чтобы не откликнуться на статью, которая неверно и крайне безграмотно трактует роль параллелизма [8]. Подобные воззрения служат питательной почвой для формирования мифов о параллелизме вообще и параллельном программировании в частности.
Во-первых, параллельное программирование – это далеко не про скорость. Скорость – лишь ее побочный эффект. Главное предназначение параллелизма – решение проблем сложности. Это когда сложная проблема разбивается на множество параллельных простых частей. Возьмем тех же философов Дейкстры: один философ – несравненно более мелкая проблема, чем задача в целом. В любой нейронной сетке множество нейронов нужны отнюдь не для увеличения скорости, а для решения сложных задач.
Во-вторых, распараллеливанию поддается любая задача, кроме элементарных. К последним относятся те, которые формально могут быть представлены автоматом без памяти (автомат с одним состоянием). А таких задач по сравнению с общим их числом ничтожно мало. А потому распараллеливанию поддается буквально все, что «движется», т.е. имеет мало-мальски сложный алгоритм.
Литература
1. Толковый словарь по вычислительным системам/Под ред. В. Иллингоута и др.: Пер. с англ. А.К. Белоцкого и др.; Под ред. Масловского. – М.: Машиностроение, 1991. – 560 с. https://libcats.org/book/764533
2. Кудрявцев В.Б., Алешин С.В., Подколзин А.С. Введение в теорию автоматов – М.: Наука. Гл. ред. физ.-мат. лит., 1985. – 320 с.
3. Мелихов А.Н. Ориентированные графы и конечные автоматы. – М.: Наука, 1971. – 416 с.
4. Поспелов Д.А. Фантазия или наука: на пути к искусственному интеллекту - М.: Наука, 1982. – 224с.,15 ил.
5. Варшавский В.И., Поспелов Д.А. Оркестр играет без дирижера: размышления об эволюции некоторых технических систем и управления ими. - М.: Наука. Главная редакция физико-математической литературы, 1984, - 208с.
6. Автоматное программирование: определение, модель, реализация. https://habr.com/ru/articles/682422/
7. Инь и ян программирования или alter ego многопоточности. https://habr.com/ru/articles/978350/
8. Больше ядер, а не более быстрые ядра. https://habr.com/ru/companies/spring_aio/articles/980220/
Комментарии (31)

rukhi7
05.01.2026 07:04Подобные задержки (уж поскольку мы их рассматриваем) фактически уже созданы.
вряд ли можно переоценить вклад простого создателя задержек в дело глобального распараллеливания. Можно пожелать только успехов тому, кто так беззаветно преданно жертвует остатками своего разума для достижения этой благородной цели глобального распараллеливания. аминь

lws0954 Автор
05.01.2026 07:04Спасибо за пожелания. Надеюсь, они искренние :)
Но "мужики-то знают" - "Всё начинается с любви… ". В нашем случае - с любви к математике. Она формализует те термины, вокруг которых ведутся все споры. С человеком, которые этого не понимает, разговаривать те то, что сложно - невозможно, т.к. языки общения разные.
Но когда "нащупаешь" истоки, то все становится сразу все ясно. Например, термины "параллелизм" или "скорость" или ... "асинхронность". Вы даже написали статью на эту тему - асинхронности. Но поскольку не задались с определением асинхронности, то получилось, может, и интересно, но как-то сумбурно и все свалилось в одну кучу ;) Это, конечно, мой взгляд, но... подобные статьи я могу читать только "по диагонали". Может и интересно, но вычленить суть довольно сложно. Хотя, казалось бы, темы-то общие.
Так и про "задержку". Я привел ее формальную модель. Математически строгую. Она объясняет, что такое задержка, какие они бывают и т.д. и т.п. Понимаете ли Вы, что такое "задержка". Какова ее роль вообще, а не только в программировании. Судя по комменту - есть определенные сомнения.
Вот как-то так. Начинать надо с "любви" (математики, конечно) :) И тогда, возможно, не придется "мучиться с многопоточностью".

eao197
05.01.2026 07:04И тогда, возможно, не придется "мучиться с многопоточностью".
И как вы умудрились при такой-то любви и таких-то знаниях так вляпаться-то?

lws0954 Автор
05.01.2026 07:04Округа так плотно "заминирована", что избежать этого практически невозможно ;)

eao197
05.01.2026 07:04Т.е. когда все те волшебные пилюли, про которые вы тут рассказываете, не помогают вам же писать многопоточный код без багов, то вам остается только расставлять смайлики и неумно шутить про "заминировано"?

lws0954 Автор
05.01.2026 07:04А вот, кстати... Что в том коде, который я привел в статье про "засады", было не так? Вы ж прочитали статью? И где там была "мина", ась? ;)

AndreyDmitriev
05.01.2026 07:04И где там была "мина"
Обычно когда демонстрируют состояние гонки в многопоточке, то пишут простое как пять копеек консольное приложение.
Если инкрементить в нескольких потоках глобальный счётчик вот так:
long g_counter = 0; // Shared counter (INTENTIONALLY non-atomic) DWORD WINAPI UnsafeThreadProc(LPVOID lpParam) { for (int i = 0; i < NUM_INCREMENTS; i++) { long local = g_counter; g_counter = local + 1; } return 0; }То очевидным образом огребём состояние гонки, когда несколько потоков прочитают и запишут одновременно.
Чтобы этого избежать, либо используют интерлок
volatile LONG g_counter = 0; DWORD WINAPI SafeThreadProc(LPVOID lpParam) { for (int i = 0; i < NUM_INCREMENTS; i++) { InterlockedIncrement(&g_counter); } return 0; }Либо критическую секцию
CRITICAL_SECTION g_Lock; DWORD WINAPI SafeThreadProc(LPVOID lpParam) { LONG newVal; EnterCriticalSection(&g_Lock); for (int i = 0; i < NUM_INCREMENTS; i++) { newVal = g_counter + 1; g_counter = newVal; } LeaveCriticalSection(&g_Lock); return 0; }Вот и всё.
Полный код - три варианта
// Небезопасный код #include <windows.h> #include <stdio.h> #define NUM_THREADS 1000 #define NUM_INCREMENTS 100000 long g_counter = 0; // Shared counter (INTENTIONALLY non-atomic) DWORD WINAPI UnsafeThreadProc(LPVOID lpParam) { for (int i = 0; i < NUM_INCREMENTS; i++) { // Non-atomic read-modify-write long local = g_counter; g_counter = local + 1; } return 0; } int main(void) { HANDLE threads[NUM_THREADS]; // Create 1000 threads for (unsigned long i = 0; i < NUM_THREADS; ++i) { threads[i] = CreateThread( NULL, // default security 0, // default stack size UnsafeThreadProc, // thread function (LPVOID)(ULONG_PTR)(i + 1), // parameter: 1..NUM_THREADS 0, // run immediately NULL // thread id (unused) ); } // Wait for all threads one by one (simplest approach; avoids 64-handle limit) for (unsigned long i = 0; i < NUM_THREADS; ++i) { WaitForSingleObject(threads[i], INFINITE); CloseHandle(threads[i]); } printf("\nExpected final count: %d\n", NUM_THREADS * NUM_INCREMENTS); printf("Actual final count (non-atomic): %ld\n", g_counter); return 0; } //============================================================================== // Безопасный код - 1 #include <windows.h> #include <stdio.h> #define NUM_THREADS 1000 #define NUM_INCREMENTS 100000 volatile LONG g_counter = 0; // Shared counter for interlocked operations (must be LONG) DWORD WINAPI SafeThreadProc(LPVOID lpParam) { unsigned long thread_num = (unsigned long)(ULONG_PTR)lpParam; // Atomic increment; returns the incremented value LONG newVal; for (int i = 0; i < NUM_INCREMENTS; i++) { newVal = InterlockedIncrement(&g_counter); } return 0; } int main(void) { HANDLE threads[NUM_THREADS]; for (unsigned long i = 0; i < NUM_THREADS; ++i) { threads[i] = CreateThread( NULL, // default security 0, // default stack size SafeThreadProc, // thread function (LPVOID)(ULONG_PTR)(i + 1), // parameter: 1..NUM_THREADS 0, // run immediately NULL // thread id (unused) ); } // Wait for all threads, then clean up for (unsigned long i = 0; i < NUM_THREADS; ++i) { WaitForSingleObject(threads[i], INFINITE); CloseHandle(threads[i]); } printf("\nExpected final count: %d\n", NUM_THREADS * NUM_INCREMENTS); printf("Actual final count (atomic): %ld\n", g_counter); return 0; } //============================================================================== // Либо так #include <windows.h> #include <stdio.h> #define NUM_THREADS 1000 #define NUM_INCREMENTS 100000 volatile LONG g_counter = 0; // Shared counter for interlocked operations (must be LONG) // Optional: serialize printf to keep lines intact (no mixed output) CRITICAL_SECTION g_Lock; DWORD WINAPI SafeThreadProc(LPVOID lpParam) { // Atomic increment; returns the incremented value LONG newVal; EnterCriticalSection(&g_Lock); for (int i = 0; i < NUM_INCREMENTS; i++) { newVal = g_counter + 1; g_counter = newVal; } LeaveCriticalSection(&g_Lock); return 0; } int main(void) { HANDLE threads[NUM_THREADS]; InitializeCriticalSection(&g_Lock); // Create 1000 threads for (unsigned long i = 0; i < NUM_THREADS; ++i) { threads[i] = CreateThread(NULL, 0, SafeThreadProc, // thread function (LPVOID)(ULONG_PTR)(i + 1), 0, // run immediately NULL ); } // Wait for all threads, then clean up for (unsigned long i = 0; i < NUM_THREADS; ++i) { WaitForSingleObject(threads[i], INFINITE); CloseHandle(threads[i]); } DeleteCriticalSection(&g_Lock); printf("\nExpected final count: %d\n", NUM_THREADS * NUM_INCREMENTS); printf("Actual final count (atomic): %ld\n", g_counter); return 0; }Если есть желание понять на самом низком уровне как это работает, то можно на ассемблере написать как-то так, используя инструкцию XCHG:
SpinLock DD 0 ; Shared spin lock (0 = unlocked, 1 = locked) Counter DQ 0 ; 64-bit shared counter ThreadProc PROC mov r8, NUM_INCREMENTS align 16 SpinWait: ; Spin lock acquire mov eax, 1 xchg eax, [SpinLock] ; Atomically try to acquire lock cmp eax, 0 ; Was lock previously 0 (unlocked)? je LockAcquired ; If yes, we acquired the lock ; If not acquired, wait and retry PAUSE ; Hint to CPU that we are in a spin-wait loop jmp SpinWait LockAcquired: ; Critical section begins - Increment 64-bit counter mov rax, [Counter] inc rax mov [Counter], rax ; Critical section ends - Release lock mov [SpinLock], 0 dec r8 ; total increments counter jnz SpinWait ; loop to the start xor eax, eax ret ENDPROC ThreadProc
lws0954 Автор
05.01.2026 07:04"Обычно когда демонстрируют состояние гонки в многопоточке, то пишут простое как пять копеек консольное приложение"
Дело, конечно, давнее, но обычно я так и делаю... Хотя по поводу того случая так не могу утверждать точно...
Но тогда проблема была совершенно в другом, т.е. совсем не в гонках и счетчике. На это было наплевать... ;)
Проблема была в том, что в режиме Debug работал проект без проблем, а в режиме Release вылетал напрочь.Переходишь в Debug - работает, Release - глюк! Стал менять конструкцию цикла в потоке и в какой-то момент все заработало. Т.е. ошибка ушла, словно и не было. И для меня это осталось загадкой до сих пор. Но в статье все это описано подробно.
.

AndreyDmitriev
05.01.2026 07:04Проблема была в том, что в режиме Debug работал проект без проблем, а в режиме Release вылетал напрочь.
А, понятно, так бывает, да.
Обычно это иллюстрируют вот таким "не надо так делать" примером:
#include <windows.h> #include <stdio.h> BOOL quit = FALSE; BOOL stop = FALSE; DWORD WINAPI SetterThread(LPVOID lpParam) { printf("Setter: started\n"); Sleep(1000); stop = TRUE; printf("Setter: stop flag set\n"); while (!quit) {} printf("Setter: stopped\n"); return 0; } DWORD WINAPI GetterThread(LPVOID lpParam) { printf("Getter: started, waiting for the stop flag...\n"); while (!stop) {} printf("Getter: got stop flag, quit\n"); quit = TRUE; return 0; } int main(void) { HANDLE hSetter = CreateThread(NULL, 0, SetterThread, NULL, 0, NULL); HANDLE hGetter = CreateThread(NULL, 0, GetterThread, NULL, 0, NULL); // Wait for both threads to complete WaitForSingleObject(hSetter, INFINITE); WaitForSingleObject(hGetter, INFINITE); CloseHandle(hSetter); CloseHandle(hGetter); return 0; }Тут проблема в том, что оптимизатор может "оптимизировать" этот код:
while (!stop) {}примерно вот так (либо в регистры процессора закеширует):
if (!stop) { for (;;) {} // infinite loop }И он никогда не завершится. Я это могу воспроизвести на древнем clang 3.3.
Лечится объявлением переменных как volatile, тогда оптимизатор их трогать не будет, либо использованием InterlockedCompareExchange, либо нормальной синхронизацией потоков через WaitForSingleObject.
Но надо смотреть в ассемблерный листинг в то место, где крэшится или встаёт колом. Обычно любому наблюдаемому и воспроизводимому феномену находится рациональное объяснение.

lws0954 Автор
05.01.2026 07:04Обычно любому наблюдаемому и воспроизводимому феномену находится рациональное объяснение.
Правильно. Что-то подобное подозревал и я. Потому и начал "копать" цикл. И результат можно именно этим и объяснить, т.е. какой-то не очень корректной (для потоков, конечно) оптимизацией. Но выяснять до конца тоже не очень большой смысл.По крайней мере для меня. Но в целом Вы все объяснили достаточно достоверно. Как бы ДНК оно и есть ДНК. Пусть оно таким и остается :) Хотя, конечно, такой опыт тоже нужен. Но это уже и есть ... "кровопускание" :(

eao197
05.01.2026 07:04Тут проблема в том, что оптимизатор может "оптимизировать" этот код:
while (!stop) {}ЕМНИП, в C++ (именно в С++, а не в чистом Си) бесконечный цикл без выраженных побочных эффектов -- это UB. И компилятор может сотворить все, что ему захочется.

lws0954 Автор
05.01.2026 07:04Где тут бесконечный цикл? Он был бы в случае: while(true){}. А тут нормальный цикл только с пустым телом цикла.

eao197
05.01.2026 07:04Компилятор не видит изменения для stop и считает, что выражение всегда истинно.

lws0954 Автор
05.01.2026 07:04Компилятор - не видит?! Да какое мне до этого дело. Если программист сказал stop, значит, - стоп! Больно умный! :)

AndreyDmitriev
05.01.2026 07:04ЕМНИП, в C++ (именно в С++, а не в чистом Си) бесконечный цикл без выраженных побочных эффектов -- это UB.
Так было в какой-то момент, но вроде P2809 теперь определяет это как легитимный код. Я походу всё время использовал for(;;); если мне где-то надо было намертво встать без особых побочных эффектов (если не обращать внимания на расход ресурсов проца, конечно, но это для быстрой проверки чего-либо, а не для продакшена).

eao197
05.01.2026 07:04Так было в какой-то момент, но вроде P2809 теперь определяет это как легитимный код.
Для многих практикующих C++ников стандарт C++26 еще не скоро станет обыденной реальностью.

eao197
05.01.2026 07:04Т.е. ошибка ушла, словно и не было. И для меня это осталось загадкой до сих пор.
Т.е. вы совершили ошибку в многопоточном коде, найти ее не смогли, написали очередную графоманскую статью о том, как вы не можете в многопоточный код, но все равно позволяете себе иронизировать над "пот, боль и кровь"?

lws0954 Автор
05.01.2026 07:04Читаете внимательно. В режиме Debug программа работала, т.е. ошибки нет и не было. Тут если и есть ошибка, то только не моя.
Кстати про ошибки. Вот сейчас бился над одной :)
Пишу продолжение статьи и делаю таймерную задержку. У меня есть вложенный автомат - CFDelay. Все, вроде, работало и вдруг - перестает. Запускаешь - глюк. Лопачу, сравниваю код - все, вроде, ОК. Натыкаюсь что в одном варианте - работает, в другом - нет. Варианты немного отличаются, но только не в логике. Но ошибка-то есть! Короче.
Вот только выяснил. Проблема в строке:
#include "./LSYSLIB/FDelay.h"
Вставлена - работает. Нет ее - глюк!!! Компиляция - на ура! Запуск - глюк! Сейчас, кода немного подправил вызов заголовков все работает. Но... что было не так-то!?
Сделать-то сделал, но ощущение "костыля" как-то не уходит.... :(
"Тяжела и неказиста жизнь простого программиста" :)

eao197
05.01.2026 07:04В режиме Debug программа работала, т.е. ошибки нет и не было. Тут если и есть ошибка, то только не моя.
Детский сад, младшая ясельная группа. Когда приложение работает в Debug-е, но падает в Release, то с вероятностью в 99% -- это баг в программе, а не в компиляторе. Особенно в многопотоке, где время работы конкретных кусков кода критически важно при обращениях к разделяемым данным. В Debug-е код работает в разы медленнее и "времянка" (есть такой старый термин) распределяется совсем не так, как в Release. Из-за чего в Release проявляются такие фокусы, до которых не сразу и додумаешься.
Блин, это прописные истины, это грабли, по которым оттаптываются все, кто хоть мало-мальски имел дело с многопоточностью.

lws0954 Автор
05.01.2026 07:04Вот про эти "грабли" и приходится разъяснять всяким разным из "ясельных групп". А то, блин, разобьют носы на всяких там качельках, а потом "плачутся" про всякие "пот, боль и кровь"... :)

eao197
05.01.2026 07:04Вот про эти "грабли" и приходится разъяснять всяким разным из "ясельных групп".
Разъяснять в стиле: "И для меня это осталось загадкой до сих пор."
Ну вперед.

AndreyDmitriev
05.01.2026 07:04Проблема в строке:
#include "./LSYSLIB/FDelay.h"
Вставлена - работает. Нет ее - глюк!!! Компиляция - на ура! Запуск - глюк! Сейчас, кода немного подправил вызов заголовков все работает. Но... что было не так-то!?
Заголовки не "вызывают", их "включают" или "подключают", если уж быть дотошным в строгом смысле терминологии.
А покажите весь проект целиком пожалуйста, вроде он был на гитхабе, так позвольте ж полюбопытствовать, что же там такого в FDelay.h, и как он там "вызывается".

lws0954 Автор
05.01.2026 07:04Извиняйте - оговорился. Кстати, когда писал, то помню был какой-то "дискомфорт", но не стал заморачиваться... :)
По поводу FDelay.h. Конечно, на гите должно все быть, если интересует. Там все до безобразия просто. Это обычный автомат, считающий такты. В одной ситуации от вызывается подобно подпрограмме, как вложенный автомат, в другой - на его базе создается процесс.
Но вот с "включением" вдруг вылезла проблема. До этого я постоянно пользовался подобной задержкой во всех ее вариантах. Но тут, как говорится: "Не было такого ни когда, а тут - опять!"

AndreyDmitriev
05.01.2026 07:04Конечно, на гите должно все быть, если интересует.
Интересует, но мои телепатические и поисковые скиллы не прокачаны настолько хорошо, чтобы найти заветную ссылку.

lws0954 Автор
05.01.2026 07:04Сейчас мы их "докачаем" ;). Смотрите мою статью. Там в конце ссылка на гит.
Про класс CFDelay. См. заголовок и реализацию - FDelay.h, FDelay.cpp. Как пользоваться - методы FCreateParDelay(), FIsActiveParDelay() для параллельной задержки и FCreateDelay(int nDelay) - для вложенной Они в классе LFsaAppl (lfsaappl.h, lfsaappl.cpp).
Но есть нюанс, который, возможно, влияет. На ESP32 - без пользовательских библиотек, в Qt - с библиотеками.

rusl002
05.01.2026 07:04параллельное программирование – это далеко не про скорость. Скорость – лишь ее побочный эффект.
Скажу крамольную мысль, но обычно ради этого побочного эффекта и мучаются с многопоточностью
eao197
Во-первых, вы искажаете смысл исходной цитаты, а именно "голая многопоточность -- это пот, боль и кровь." Т.е. не многопоточность вообще, а многопоточность на базе самых низкоуровневых примитивов, от atomic-ов и на коленке склепанных самостоятельно lock-free структур до semaphores, mutexes, events и condition variables. К счастью, есть более высокоуровневые подходы, очень сильно (я бы даже сказал драматически) упрощающие многопоточное программирование (но это обходится далеко не бесплатно).
Во-вторых, есть другой термин для обозначения человека, рассуждающего о вещах, в которых он не разбирается.
А мужики-то и не знают...