brag 0 14 ноября, 2021 Опубликовано 14 ноября, 2021 · Жалоба 9 hours ago, jcxz said: Без разницы, откуда угодно. Это текущие данные, показания различных датчиков. Работает какая-то периферия (АЦП например), результаты обработки данных полученных от неё и печатаются на этой странице. Каждому из подключенных клиентов. Давайте конкретный пример, покажу, как делается ;)) Но даже здесь. Как будете добывать текущие данные с АЦП и как будете печатать? Можно псевдокод? Именно того участка, где данные передаются с периферии к нашим максимум 5 блокирующимся в putc процессам(потокам). Данные с АЦП как возникают? Прерывание adc_isr() или может быть busy loop while(adc_reg.dataready); Как именно? Есть ли у АЦП какой-то fifo или просто регистр ADCDAT? Какая частота? Какая пропускная способность TCP-соединения? Что если она в эквиваленте ниже, чем частота АЦП? 9 hours ago, jcxz said: Ну да сложнее - МНОГОКРАТНО сложнее для моего примера. Который в случае блокирующего доступа решается элементарно, с вашим способом станет просто монстроидальным и практически нереализуемым. Давайте пример, посмотри на сколько будет сложнее или проще 9 hours ago, jcxz said: Это не так. Во многих случаях блокирующий метод гораздо проще, нагляднее, а значит - работоспособнее. В каких? Покажите хоть один конкретный случай. amaora показал и я сразу выдал асинхронное решение, хоть и задача была спроектирована под блок сложнее оно особо не стало. 9 hours ago, jcxz said: это говорит только о вашем фанатизме. "Назло бабушке отморожу уши". Именно так работает Javascript да и любой аналог console.log Будет работать до тех пор, пока не закончится память. А когда закончится данные начнут улетать в пропасть. Для важных данных применяются asyc io: fs.appendFile(path, data[, options], callback) Цитата Поделиться сообщением Ссылка на сообщение Поделиться на другие сайты Поделиться
brag 0 14 ноября, 2021 Опубликовано 14 ноября, 2021 · Жалоба 10 hours ago, jcxz said: И применяют когда нужно. А когда нужно - блокирущий метод. Согласен. Но оба эти подхода несовместимы практически. Если в FSM применить блокирующую функцию - вся FSM зависнет и не сможет реагировать на другие события(входы). Поэтому на практике задачи решаются либо тем, либо другим способом. При попытке их комбинировать получается АД. 10 hours ago, jcxz said: Когда вы говорите, что используете их всегда, даже когда оно криво работает Не криво, а так, как требует условия задачи. Если в задаче сказано, что в случаи нехватки памяти лог можно терять, значит так и нужно, при этом а) программа должна продолжать работу корректно. б) печать лога никак не должна сказываться на обработке важных событий. Цитата Поделиться сообщением Ссылка на сообщение Поделиться на другие сайты Поделиться
brag 0 14 ноября, 2021 Опубликовано 14 ноября, 2021 · Жалоба Теперь пример веб сервера. У нас есть АЦП с тактовой 100kHz и 10 каналов. Выборки терять нельзя. Буфера нет, точнее он одноуровневый - туда лезет всего одна выборка. Дальше у нас есть функция, подобна той, которую привел amaora. Она принимает на вход эти данные, и выдает выход вида float y[8]; Частота выходных выборок достоверно неизвестна, поскольку зависит от самих данных на входе(как у amaora), но мы точно знаем, что она не выше 1кгц. Данных может и вовсе не быть на выходе долгое время, поскольку функция на основе полученных данных пока не смогла найти решение. Также у нас есть UART 115200 бит/s на который надо в виде неких пакетов выводить данные. Допуcтим это будет (условный) TCP/IP протокол. Особо вдаваться в подробности тут не будем, достаточно одного условия - данные идут не непрерывно, а пакетами(фреймами) по 256 байт. Нужно сделать веб-сервер, который одновременно будет обслуживать до 5 клиентов(через один uart). По GET запросу должен выдаваться HTML-документ вида <html><body><h1>timestamp</h1> <table><tr><td>y[0]</td></tr><tr><td>y[1]</td></tr>.....</table></body></html> Размер этого документа неизвестен, но он заведомо больше того, что лезет в 1 фрейм. timestamp - это время последнего результата, полученного на выходе нашей функции. В формате hh:mm:ss.y - собственно последний результат. Предыдущие результаты нас не интересуют и их можно смело терять. Если результата все еще нет нужно вывести <h1>No result yet</h1> и таблицу не выводить вовсе. Производительности процессора заведомо достаточно, чтобы наша функция при любых раскладах смогла обработать входной поток с частотой 100кгц. А также ее заведомо достаточно, для выполнения всей задачи. НО время выполнения самой функции иногда может доходить до 200мкс, что значительно превышает периодичность поступления данных по UART 8/115200 = 69мкс, а так же периодичность данных АЦП 10мкс. Данные UART и АЦП терять нельзя! Предлагаю всем желающим решить эту задачу блокирующим способом. А в следующих постах я решу ее асинхронным. Вдаваться в конкретные реализации драйверов и ОС не нужно, достаточно самой концепции. Цитата Поделиться сообщением Ссылка на сообщение Поделиться на другие сайты Поделиться
brag 0 14 ноября, 2021 Опубликовано 14 ноября, 2021 · Жалоба Итак, асинхронная реализация первой части задачи - АЦП и обработка данных Spoiler struct Input { float x[10]; }; struct Output { float y[8]; int timestamp; bool out_valid; }; // собственно наша функция void heavy_function(const struct Input*, void(*on_out_ready)(struct Output*)); // // По условию задачи время выполнения heavy_function может доходить до 200мкс // При частоте АЦП 100кгц достаточно буфера размером 20 выборок. Выделим с запасом - 32шт typedef NonIntrusiveQueue<Input, 32> Queue_t; struct Adc { Queue_t queue; struct Output last_out; }; static struct Adc g_adc; // это прерывание. оно имеет наивысший приоритет, который равен 10 // Оно может прерывать все, включая другие прерывания, приориоритет которых ниже 10 // Реализация этой возможности аппаратная, например NVIC. Нам ничего дополнительно делать не нужно void isr_ADC(void) { struct Input in; // как можно скорее получим данные с регистров АЦП for(int i=0; i<10; i++) { in.x[i] = ADC_REG[i]; }; g_adc.queue.push(in); } void adc_init(void) { g_adc.queue.set_ready_handler(&on_adc_queue_data_ready, 5); // 5 is a handler priority g_adc.last_out.valid = false; } // ------------------------------------------------- // А это обработчик события. Его приоритет равен 5. // Он может вытеснять(прерывать) другие обработчики, с более низким приоритетом // Он выполняется тогда, когда очередь переходит из состояния 'пустая' в 'не пустая' static void on_adc_queue_data_ready(Queue_t *queue) { struct Input x; // pop неблокирующий. Если очередь пустая - вернет false, иначе достанет 1 элемент и вернет true while(queue->pop(&x)){ // собственно наша функция heavy_function(&x, &on_output_ready); } } static void on_output_ready(struct Output *y) { out->timestamp = now(); // получаем текущее время. Реализацию самого now пока упустим out->valid = true; // защищаем данные короткой критической секцией // Всякие fence упустим, global_disable_interrupts/global_enable_interrupts все сделают за наc global_disable_interrupts(); g_adc.out = *out; global_enable_interrupts(); } struct Output get_last_output(void) { struct Output out; // защищаем данные короткой критической секцией global_disable_interrupts(); out = g_adc.out; global_enable_interrupts(); return out; } 75 строк кода с комментами. Попробуйте реализовать это блокирующим подходом, сравним сложность и размер кода ;) Продолжение следует Цитата Поделиться сообщением Ссылка на сообщение Поделиться на другие сайты Поделиться
brag 0 14 ноября, 2021 Опубликовано 14 ноября, 2021 · Жалоба Вот реализация нашего uart/tcp-ip. Нужная нам функция тут одна void uart_tcp_write(const uint8_t *p_data, int data_size, Callback_T completion_callback) она пакует наши данные в фреймы и отправляет эти фреймы в uart. Но ее нельзя вызывать повторно, пока не отработает предыдущая, то есть пока не получим completion_callback. Нас это совершенно устраивает, потом увидим почему. Spoiler // Время выполнения всех витесняющих нас прерываний очень короткое, не превышает 2мкc // При частоте UART 115200 бит/с = 14400 байт/c интервал будет 69мкс. А значит 1-байтового регистра // достаточно для того, чтобы данные не терялись. // По условию задачи время выполнения heavy_function может доходить до 200мкс, которая // будет вытеснять uart-обработчики. 200/69 ~= 3, 3 байта достаточно // Выделим с запасом - 8 байт typedef NonIntrusiveQueue<uint8_t, 8> UartQueue_t; struct Uart { UartQueue_t rx_queue; SST_Event tx_complete_event; int tx_frame_counter; uint8_t tx_frame[256]; // Собственно наш TCP-фрейм буфер const uint8_t *p_tx_data; int tx_data_size; int tx_data_ctr; Callback_T tx_data_completion_callback; }; static const struct Uart g_uart; // Это прерывание. Его приоритет равен 8 // Оно может прерывать все, включая другие прерывания, приориоритет которых ниже 8 // Реализация этой возможности аппаратная, например NVIC. Нам ничего дополнительно делать не нужно void isr_UART_RX(void) { g_uart.rx_queue.push(UART_REG_RX); } // Это тоже прерывание. Его приоритет равен 7 // Оно может прерывать все, включая другие прерывания, приориоритет которых ниже 7 // Реализация этой возможности аппаратная, например NVIC. Нам ничего дополнительно делать не нужно // Оно выполняется всегда, когда UART Shift Register пустой void isr_UART_TX(void) { UART_REG_TX = g_uart.tx_frame[g_uart.tx_frame_counter]; g_uart.tx_frame_counter++; if(g_uart.tx_frame_counter >= 256) { diable_uart_tx_interrupt(); // отправляем событие в очередь на выполнение sst_post(&g_uart.tx_complete_event); } } void uart_init(void) { // 3 is a handler priority g_uart.queue.set_ready_handler(&on_uart_rx_ready, 3); sst_event_init(&g_uart.tx_complete_event, &uart_frame_tx_complete, 3); enable_uart_rx_interrupt(); // uart TX interrupt is disabled by default } // это обработчик события. Его приоритет равен 3. // Он может вытеснять(прерывать) другие обработчики, с более низким приоритетом // А также его могут вытеснять любые прерывания и другие, более высокоприоритетные обработчики событий, например on_adc_queue_data_ready static void on_uart_frame_tx_complete(void) { g_uart.tx_frame_counter = 0; uart_tx_fsm(); } // Весь код ниже тоже ессно выполняется с приоритетом 3 static void uart_frame_tx_start(void) { g_uart.tx_frame_counter = 0; enable_uart_interrupt(); } static void uart_tx_fsm(void) { const int n = min( g_uart.tx_data_size - g_uart.tx_data_ctr, 256 - g_uart.tx_frame_counter ); for(int i=0; i<n; i++) { g_uart.tx_frame[i+g_uart.tx_frame_counter] = g_uart.p_tx_data[i+g_uart.tx_data_ctr]; } g_uart.tx_frame_counter += n; g_uart.tx_data_ctr += n; if(g_uart.tx_frame_counter >= 256) { uart_frame_tx_start(); } else if(g_uart.tx_data_ctr >= tx_data_size) { invoke(&g_uart.tx_data_completion_callback); } } void uart_tcp_write(const uint8_t *p_data, int data_size, Callback_T completion_callback) { g_uart.tx_data_ctr = 0; g_uart.p_tx_data = p_tx_data; g_uart.tx_data_size = tx_data_size; g_uart.tx_data_completion_callback = callback; uart_tx_fsm(); } RX-данные пока просто складываются в очередь. Hикак не обрабатываются, но нам это пока и не нужно. Допустим, они обрабатываются корректно. Позже, если понадобится, покажу и их обработку. А теперь попробуйте реализовать uart_tcp_write блокирующим способом, да так, чтобы ваш тоже блокирующий АЦП-код работал корректно. ;) Цитата Поделиться сообщением Ссылка на сообщение Поделиться на другие сайты Поделиться
x893 60 14 ноября, 2021 Опубликовано 14 ноября, 2021 · Жалоба А из-за чего сыр-бор ? Всё очевидно и логично. Читал-читал - не понял суть спора. Цитата Поделиться сообщением Ссылка на сообщение Поделиться на другие сайты Поделиться
amaora 25 14 ноября, 2021 Опубликовано 14 ноября, 2021 (изменено) · Жалоба 17 hours ago, brag said: Вот, переделал Ваш пример в асинхронный вид. Тупо взял и заменил block на nonblock. Для подобных методов (алгоритмов) возможно спекулятивное выполнение при условии, что есть возможность вычислять целевую функцию в нескольких точках одновременно. То есть не ждать результата pfun, чтобы сделать сравнение и выбрать дальнейшую ветвь, а забросить сразу несколько вычислений pfun, и когда они будут готовы продвинуться уже на несколько шагов (да при этом часть вычислений pfun будут неиспользованы). Мне это не сильно надо, но было интересно подумать в каком виде можно записать подобный алгоритм, чтобы спекулятивное выполнение добавлялось автоматически. Всегда хочется записывать алгоритм линейно, как это делают на псевдокоде или словами, но при этом иметь возможность преобразования (компиляции) в nonblock со спекуляцией. Что вручную можно переделать в nonblock я не сомневался, но мне такой вид кажется менее читаемым. Изменено 14 ноября, 2021 пользователем amaora Цитата Поделиться сообщением Ссылка на сообщение Поделиться на другие сайты Поделиться
amaora 25 14 ноября, 2021 Опубликовано 14 ноября, 2021 · Жалоба 4 hours ago, brag said: Теперь пример веб сервера. У нас есть АЦП с тактовой 100kHz и 10 каналов. Выборки терять нельзя. Буфера нет, точнее он одноуровневый - туда лезет всего одна выборка. ... Наверно сделал бы очередь в прерывании АЦП и там же запускал бы ещё одно прерывание-обработчик с приоритетом пониже, а в нем уже уже выгребал бы очередь и запускал тяжёлую функцию обработки. Оставшаяся часть в медленных RTOS-тредах (никогда не делал веб-серверов). Ну пусть будет пул тредов (не обязательно по количетву клиентов, можно меньше) для обработки каждого GET запроса и одна задача обрабатывающая сами запросы и передающая их в свободный тред-отбработчик. Все связи между задачами на очередях, получение и отправка сетевых пакетов через очереди. Получение данных от функции обработки не знаю как, по условию не совсем ясно, вероятно wait-free буферизация на атомарных операциях или блокировка прерываний на время чтения всего блока переменных float y[8]. Цитата Поделиться сообщением Ссылка на сообщение Поделиться на другие сайты Поделиться
brag 0 14 ноября, 2021 Опубликовано 14 ноября, 2021 · Жалоба 5 hours ago, amaora said: Что вручную можно переделать в nonblock я не сомневался, но мне такой вид кажется менее читаемым. Дело привычки ;) 5 hours ago, amaora said: Всегда хочется записывать алгоритм линейно, как это делают на псевдокоде или словами, но при этом иметь возможность преобразования (компиляции) в nonblock со спекуляцией. Я думаю, что это невозможно. Линейный код будет только тогда, когда задача линейная. Например классические команды OS Unix - ls, rm итд. НО не cp, нормальный cp в чисто блокирующем коде, то есть без привлечения асинхронного :)) драйвера реализовать практически невозможно. Когда программа интерактивная, она в принципе не может быть линейной. Даже самая простая, из диода и двух кнопок. 5 hours ago, amaora said: Мне это не сильно надо, но было интересно подумать в каком виде можно записать подобный алгоритм, чтобы спекулятивное выполнение добавлялось автоматически. Да такие задачи решаются довольно часто. И очень хорошо ложатся на неблокирующий подход. 5 hours ago, amaora said: Наверно сделал бы очередь в прерывании АЦП и там же запускал бы ещё одно прерывание-обработчик с приоритетом пониже, а в нем уже уже выгребал бы очередь и запускал тяжёлую функцию обработки. Именно это мы видим в моей реализации выше ;) А по-другому никак! Теоретически можно реализовать на тредах, но код будет адище. 5 hours ago, amaora said: Оставшаяся часть в медленных RTOS-тредах (никогда не делал веб-серверов). А вот с этого места хотелось бы по-подробнее. Смотрите, есть UART(регистры rx_dat, tx_dat и 2 прерывания: rx_full, tx_empty), и есть блокирующая ОС, что делаем дальше? Как будем организовывить работу с UART с тредов? 5 hours ago, amaora said: Ну пусть будет пул тредов (не обязательно по количетву клиентов, можно меньше) ок. есть пул тредов Thread pool[5]; void Thread::get_request_thread() { // что делаем здесь??? } 5 hours ago, amaora said: и одна задача обрабатывающая сами запросы и передающая их в свободный тред-отбработчик. Как эта задача будет получать запросы и как передавать дальше на обработку? Как будет вести учет свободных тредов? 5 hours ago, amaora said: . Все связи между задачами на очередях, получение и отправка сетевых пакетов через очереди. Ок. Какие конкретно нужны очереди, какого размера и сколько штук? Чтобы гарантировано хватило и программа не заглючила ни при каких обстоятельствах? То есть чтобы ни один пакет не был утерян, чтобы ни одна АЦП выборка не была утеряна. 5 hours ago, amaora said: Получение данных от функции обработки не знаю как, по условию не совсем ясно, вероятно wait-free буферизация на атомарных операциях или блокировка прерываний на время чтения всего блока переменных float y[8]. Именно последний способ применен у меня в коде, его можно использовать и в тредовом подходе. struct Output get_last_output(void) { struct Output out; // защищаем данные короткой критической секцией global_disable_interrupts(); out = g_adc.out; global_enable_interrupts(); return out; } Цитата Поделиться сообщением Ссылка на сообщение Поделиться на другие сайты Поделиться
brag 0 14 ноября, 2021 Опубликовано 14 ноября, 2021 · Жалоба Теперь реализация самого сервера. Все ессно в один поток одного приоритета. Задачка в общем то тривиальная, такую решают все начинающие node-js программисты. Spoiler struct Connection { struct Socket s; struct Output output; int oidx; char buffer[64]; }; struct Request { int n; struct Connection conns[5]; }; // Это вся память, что нам потребуется, больше ничего, никаких контекстов и стеков static struct Request g_req; void init(void) { g_req.n = 0; } // Это обработчик приоритет 3 // Его запускает наш TCP-стек, который берет начало от isr_UART_RX. Реализацию которого мы пока упустили. При необходимости сделаем и его // Запускается это как только приходит входящее соединение с запросом GET void on_get_request(struct Socket *s) { g_req.n++; if(g_req.n > 5) { socket_close(s); return; } struct Connection *conn = g_req.conns[g_req.n-1]; conn->s = *s; conn->output = get_last_output(); conn->oidx = 0; // это большая статическая часть - начало нашей веб-страницы const char *s1 = "<html><head><title>....</title></head><body>"; socket_write(s, s1, sizeof(s1)-1, (struct Callback){ &on_s1_write_complete, conn }); } // А это запустится, когда соединение закроется. Название говорит само за себя :)) void on_socket_close(struct Socket *s) { g_req.n--; } static void on_s1_write_complete(void *that) { struct Connection *conn = that; if(conn->output.out_valid) { int n = sprintf(conn->buffer, "<h1>%.2d:%.2d:%.2d</h1><table>", conn->output.timestamp/3600, conn->output.timestamp/60 % 60, conn->output.timestamp % 60); socket_write(&conn->s, conn->buffer, n, (struct Callback){ &write_data, conn }); } else { const char *s1 = "<h1>No result yet</h1>"; socket_write(&conn->s, s1, sizeof(s1)-1, (struct Callback){ &write_foother, conn }); } } static void write_data(void *that) { struct Connection *conn = that; if(conn->oidx < 8) { int n = sprintf(conn->buffer, "<tr><td>%f</td></tr>", conn->output.y[conn->oidx]); conn->oidx++; socket_write(&conn->s, conn->buffer, n, (struct Callback){ &write_data, conn }); } else { const char *s1 = "</table>"; socket_write(&conn->s, s1, sizeof(s1)-1, (struct Callback){ &write_foother, conn }); } } static void write_foother(void *that) { struct Connection *conn = that; const char *s1 = "</body></html>"; socket_write(&conn->s, s1, sizeof(s1)-1, (struct Callback){ &on_foother_complete, conn }); } static void on_foother_complete(void *that) { struct Connection *conn = that; socket_close(&conn->s); } Осталась реализация socket_write. Она в общем то простейшая, то об этом в следующий раз Цитата Поделиться сообщением Ссылка на сообщение Поделиться на другие сайты Поделиться
jcxz 241 14 ноября, 2021 Опубликовано 14 ноября, 2021 · Жалоба 3 часа назад, brag сказал: Вот реализация нашего uart/tcp-ip. И где там HTTP-сервер? Где печать HTML? Причём тут UART, если я спрашивал про ETHERNET? Зачем пытаетесь забалтывать? Есть что сказать по теме - напишите, а мусор сыпать не надо. 2 часа назад, x893 сказал: А из-за чего сыр-бор ? Всё очевидно и логично. Читал-читал - не понял суть спора. Очередной пророк автоматного программирования пытается набрать здесь себе паству. 14 минут назад, brag сказал: Теперь реализация самого сервера. Все ессно в один поток одного приоритета. PS: Ладно - вопрос риторический. Не будем вас переубеждать. Набирайте себе паству поклонников. Цитата Поделиться сообщением Ссылка на сообщение Поделиться на другие сайты Поделиться
brag 0 14 ноября, 2021 Опубликовано 14 ноября, 2021 · Жалоба 52 minutes ago, jcxz said: И где там HTTP-сервер? Где печать HTML? здесь https://electronix.ru/forum/index.php?app=forums&module=forums&controller=topic&id=136908&do=findComment&comment=1784127 https://nodejs.org/en/knowledge/HTTP/servers/how-to-create-a-HTTP-server/ 52 minutes ago, jcxz said: Причём тут UART, если я спрашивал про ETHERNET? какая разница uart или ethernet? оба интерфейса последовательны, разница лишь в протоколах. Да хоть pci-express с сотней слоев, сути это не меняет. 55 minutes ago, jcxz said: Очередной пророк автоматного программирования пытается набрать здесь себе паству. Тема практическая, про реализацию задач автоматным и блокирующим способом, их сравнение, преимущества и недостатки каждого. Очень желательно с реальным примером кода. Цитата Поделиться сообщением Ссылка на сообщение Поделиться на другие сайты Поделиться
x893 60 15 ноября, 2021 Опубликовано 15 ноября, 2021 · Жалоба 4 hours ago, brag said: Теперь реализация самого сервера ... Так это и так всё известно. Что тут спорить ? Статическая память и callback's. Но если понадобится 10 соединений, а не 5, то всё - только перекомпиляция. Из-за чего сыр-бор непонятно. Цитата Поделиться сообщением Ссылка на сообщение Поделиться на другие сайты Поделиться
xvr 12 15 ноября, 2021 Опубликовано 15 ноября, 2021 · Жалоба Да будет срач! 4 года прошло, можно всё по второму кругу обсудить :) Async подход хорошо прижился в Python 3 и JS (парочка async/await). Но там очень помогает собственно интерпретатор - он берёт на себя заботу по преобразованию последовательного кода в машину состояний (по сути). От программиста требуется только разметить асинхронный функции (async) и точки их вызова (await). Но даже для такого упрощённого использования уже видно, что превратить чисто последовательный код в асинхронный просто так не получится - как минимум эти 2 волшебных слова придётся проставить везде. Ни С ни С++ не предоставляют такой возможности (async/await), так что приходится кишки кода разматывать вручную. У асинхронного подхода есть одно неоспоримое преимущество - количество точек синхронизации может быть любым (они ресурсы не тянут), для thread'ов придётся заводить отдельные thread'ы для групп таких точек. Условный пример - web сервер для одновременного обслуживания 1000 клиентов, построенный на thread'ах (чисто последовательный - по штуке на thread), будет нуждаться в 1000 thread'ах. Асинхронный всё уложит в один thread. Цитата Поделиться сообщением Ссылка на сообщение Поделиться на другие сайты Поделиться
brag 0 15 ноября, 2021 Опубликовано 15 ноября, 2021 · Жалоба 9 hours ago, x893 said: Но если понадобится 10 соединений, а не 5, то всё - только перекомпиляция. А иначе никак, хоть callbacks, хоть blocking. Если на этапе проектирования не было заложено больше - ничего не выйдет. Можно динамическую память пристроить легко, но тогда все равно упремся в лимит размера кучи(пула) заданный на этапе компиляции или(что куда хуже) в физический лимит RAM 47 minutes ago, xvr said: волшебных слова придётся проставить везде. Волшебства там нет, async/await это просто синтаксический сахар над Primise-ами. Код все равно не будет линейным, он по прежнему остается асинхронным, просто синтаксически(отдаленно) похож на линейный. А Primise - ни что иное, как обычные конечные автоматы. И на C/C++ можно реализовать их, только без(много-много) динамической памяти толку от них 0. 55 minutes ago, xvr said: У асинхронного подхода есть одно неоспоримое преимущество - количество точек синхронизации может быть любым (они ресурсы не тянут), для thread'ов придётся заводить отдельные thread'ы для групп таких точек. Условный пример - web сервер для одновременного обслуживания 1000 клиентов, построенный на thread'ах (чисто последовательный - по штуке на thread), будет нуждаться в 1000 thread'ах. Асинхронный всё уложит в один thread. Да, это и экономия ресурсов и простота управления множеством конкурентных событий. Весь современный софт движется в сторону асинхронного - появление таких вещей, как NodeJS, Promise и интеграция их в языки программирования, поддержка языками async/await, синтаксические решения в языке Kotlin итд - тому доказательства. Может и через некоторое время мы и для embedded получим какой-то язык, который будет как-то помогать нам работать со стейтом. А пока лучшего решения, чем явно определять структуры и гнать его в виде указателя that нет. Но я привык, мне норм ;)) Thread, без прикручивания к нему асинхронных фишек, например древнейших unix-сигналов )) больше одного события обработать не может, а обычынй копеечный автомат может хоть сотни тысяч Цитата Поделиться сообщением Ссылка на сообщение Поделиться на другие сайты Поделиться