Какой архитектурный паттерн для обработки конкурентных операций основан на цикле, ожидающем события, а не на вытесняющей многозадачности?

Ответ

Этот подход называется Event Loop (цикл событий) или Event-Driven Architecture (событийно-ориентированная архитектура).

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

Отличие от модели Go: Важно понимать, что Go не использует Event Loop как основную модель конкурентности. Вместо этого Go использует горутины (goroutines) и планировщик (scheduler), который эффективно распределяет горутины по системным потокам. Модель Go (CSP) является более высокоуровневой.

Однако, конструкция select позволяет горутине реагировать на несколько событий из разных каналов, что концептуально похоже на ожидание событий в Event Loop:

for {
    select {
    case msg := <-messages: // Ожидание сообщения
        handleMessage(msg)
    case <-time.After(1 * time.Second): // Ожидание таймера
        handleTimeout()
    }
}

Где применяется: Этот паттерн является основой для таких технологий, как Node.js, Nginx, Redis.

Ответ 18+ 🔞

А, слушай, вот эта штука, Event Loop, она же цикл событий, или, по-умному, событийно-ориентированная архитектура. Суть-то проще пареной репы, если не забивать голову терминами.

Вместо того чтобы плодить потоки или процессы на каждую мелкую задачу — типа на каждого нового клиента заводить отдельного виртуального работника — всё запихивается в один-единственный, блядь, главный цикл. Этот цикл сидит, как охранник на вахте, и ждёт: а ну-ка, что у нас там случилось? Новый запрос пришёл? Файл прочитался? Таймер дрыгнул? Как только событие случается — цикл тут же вызывает привязанную к нему функцию-обработчик. И всё это, внимание, должно работать неблокирующе, иначе весь этот цирк с конями встанет колом. Представь, один чувак в очереди на кассе застрял с подсчётом мелочи — и всё, пипец, все стоят.

А теперь, ёпта, важный момент! Многие думают, что Go так и работает. А вот нихуя! Go — он хитрожопый. У него своя модель: горутины и их планировщик. Это как если бы у тебя был умный прораб, который тысячу разнорабочих (горутин) раскидывает по нескольким настоящим бригадам (потокам ОС). Модель Go (CSP) — это уровень выше, она для нас, ленивых программистов, чтобы не париться с низкоуровневой синхронизацией.

Но! Есть в Go одна конструкция, select, которая делает реверанс в сторону Event Loop'а. Она позволяет горутине ждать событий сразу из нескольких каналов, прямо как тот самый цикл.

for {
    select {
    case msg := <-messages: // Ждём сообщения
        handleMessage(msg)
    case <-time.After(1 * time.Second): // Ждём, пока таймер дрыгнет
        handleTimeout()
    }
}

Видишь? Сидит горутина, никого не трогает, и слушает: а не пришло ли чего в канал messages? Или не пора ли уже по таймауту ругаться? Концептуально — один в один ожидание событий.

А где эта петля, блядь, водится в дикой природе? Да везде! Это же фундамент, на котором стоят Node.js, Nginx, Redis. Без этого подхода они бы просто сожрали всю оперативку, создавая поток на каждое подключение. А так — один цикл, да овердохуища событий. Красота!