Tarantool CE/EE Documentation portal logo
Помощь
Обновлена 15 сентября 2026 г. в 08:55

Файберы, передача управления и кооперативная многозадачность

Создание файбера (fiber) — это способ Tarantool обеспечить постоянную работу логики приложения в фоновом режиме. Файбер — это набор инструкций, выполняемых с кооперативной многозадачностью: инструкции содержат сигналы передачи управления (yield), при которых управление передаётся другому файберу.

Файберы

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

  • Лучшая управляемость. Потоки часто зависят от планировщика потоков ядра для вытеснения занятого потока и возобновления другого, поэтому вытеснение может происходить непредсказуемо. Файберы сами передают управление для запуска другого файбера во время выполнения, поэтому передача управления контролируется логикой приложения.

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

Однако у файберов есть некоторые ограничения по сравнению с потоками, главное из которых — отсутствие многоядерного режима. Все файберы в приложении принадлежат одному потоку, поэтому они используют то же ядро процессора, что и родительский поток. Тем не менее, для приложений Tarantool это ограничение не является существенным, так как типичное узкое место для Tarantool — это жёсткий диск, а не процессор.

Файбер обладает всеми возможностями корутины в Lua, и все концепции программирования, применимые к Lua-корутинам, применимы и к файберам. Однако в Tarantool внесены некоторые улучшения для файберов, и они используются внутри системы. Поэтому, хотя использование корутин возможно и поддерживается, рекомендуется использовать файберы.

Любой активный файбер может находиться в одном из трёх состояний: running, suspended и ready. После завершения файбера возвращается статус dead.

Подробнее о файберах см. в документации модуля fiber.

Передача управления

Передача управления (yield) — это действие в кооперативной среде, которое передаёт управление потоком от текущего файбера другому файберу, готовому к выполнению.

Любой активный файбер может находиться в одном из трёх состояний: running, suspended и ready. После завершения файбера возвращается статус dead. При наблюдении за файберами извне можно увидеть только running (для текущего файбера) и suspended для любого другого файбера, ожидающего события от цикла событий (ev) для выполнения.

image

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

После того как файбер передал управление и снова получил его, он немедленно вызывает testcancel.

Передача управления может быть явной или неявной.

Явная передача управления

Явная передача управления очевидна из вызывающего кода. Существует только два явных способа передачи управления: fiber.yield() и fiber.sleep(t).

  • fiber.yield() передаёт выполнение другому файберу в состоянии ready, переводя себя в состояние ready, что означает, что он будет выполнен снова как можно скорее, уступая при этом место другим файберам, ожидающим выполнения.

  • fiber.sleep(t) передаёт выполнение другому файберу в состоянии ready и переводит себя в состояние suspended на время t, пока не пройдёт заданное время и цикл событий не переведёт этот файбер в состояние ready. В целом, для длительных ресурсоёмких задач рекомендуется периодически передавать управление, чтобы кооперативно уступать место другим ожидающим файберам.

Неявная передача управления

С другой стороны, существует множество операций, таких как операции с сокетами, файловой системой и дисковым вводом-выводом, которые предполагают некоторое ожидание для текущего файбера, в то время как другие могут выполняться. При возникновении такой операции потенциально блокирующая операция передаётся в цикл событий, а файбер приостанавливается до тех пор, пока ресурс не будет готов для продолжения выполнения.

Список операций с неявной передачей управления:

  • Установка соединения (socket).
  • Чтение и запись сокета (socket).
  • Операции файловой системы (из fio).
  • Передача данных через каналы (fiber.channel).
  • Файловый ввод-вывод (из fio).
  • Операции консоли (поскольку консоль является сокетом).
  • HTTP-запросы (поскольку HTTP — это операция с сокетом).
  • Модификации базы данных (если они предполагают запись на диск).
  • Чтение базы данных для движка vinyl.
  • Вызов другого процесса (popen).

Для memtx, поскольку все данные находятся в памяти, передача управления при запросах на чтение (таких как :select, :pairs, :get) не происходит.

Для vinyl, поскольку часть данных может отсутствовать в памяти, при чтении возможен дисковый ввод-вывод (для выборки данных с диска), а при записи — задержка в ожидании освобождения памяти.

Для memtx и vinyl, поскольку запросы на изменение данных должны записываться в WAL, обычно выполняется box.commit().

В режиме autocommit по умолчанию передача управления происходит при следующих операциях:

Для обеспечения атомарности транзакций в транзакционном режиме к операциям модификации движка memtx применяются некоторые изменения. После выполнения box.begin или внутри вызова box.atomic ни одна операция модификации не передаёт управление — передача происходит только при box.commit или при возврате из box.atomic. При этом box.rollback не передаёт управление.

Именно поэтому выполнение отдельных команд, таких как select(), insert(), update(), в консоли внутри транзакции без MVCC приведёт к её прерыванию. Это связано с неявной передачей управления после выполнения каждого фрагмента кода в консоли.

Пример №1

  • Движок = memtx.
space:get()space:insert()

В этой последовательности происходит одна передача управления — в конце вставки (insert), вызванной неявным коммитом; get() не записывает ничего в WAL и поэтому не передаёт управление.

  • Движок = memtx.
box.begin()space1:get()space1:insert()space2:get()space2:insert()box.commit()

В этой последовательности происходит одна передача управления — в конце box.commit, ни одна вставка (insert) не передаёт управление.

  • Движок = vinyl.
space:get()space:insert()

В этой последовательности может быть от одной до трёх передач управления: get() может передать управление, если данных нет в кэше, insert() может передать управление при ожидании доступной памяти, а также происходит неявная передача управления при коммите.

  • Движок = vinyl.
box.begin()space1:get()space1:insert()space2:get()space2:insert()box.commit()

В этой последовательности команд передача управления может произойти от 1 до 5 раз.

Пример №2

Предположим, что в спейсе tester движка memtx есть кортежи, в которых третье поле содержит положительную сумму в долларах.

Начнём транзакцию, снимем сумму с кортежа №1, внесём на кортеж №2 и завершим транзакцию, сделав её результаты постоянными.

tarantool> function txn_example(from, to, amount_of_money)         >   box.atomic(function()         >     box.space.tester:update(from, {{'-', 3, amount_of_money}})         >     box.space.tester:update(to,   {{'+', 3, amount_of_money}})         >   end)         >   return "ok"         > endResult:---...tarantool> txn_example({999}, {1000}, 1.00)---- "ok"...

Если wal_mode = none, то неявной передачи управления при коммите не происходит, так как нет записей в WAL.

Если запрос выполняется через сетевой коннектор, такой как net.box, и предполагает отправку запросов на сервер и получение ответов, то это включает сетевой ввод-вывод и, следовательно, неявную передачу управления. Даже если запрос, отправляемый на сервер, не содержит неявной передачи управления. Поэтому следующая последовательность вызывает передачу управления три раза подряд — при отправке запросов в сеть и ожидании результатов.

conn.space.test:get{1}conn.space.test:get{2}conn.space.test:get{3}

Кооперативная многозадачность

Кооперативная многозадачность означает, что если активный файбер намеренно не передаёт управление, он не вытесняется другим файбером. Но активный файбер намеренно передаёт управление при достижении «точки передачи»: коммита транзакции, системного вызова или явного запроса передачи управления. Любой системный вызов, который может заблокировать выполнение, выполняется асинхронно, и любой активный файбер, ожидающий системного вызова, вытесняется, чтобы его место занял другой готовый к выполнению файбер и стал новым активным файбером.

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

Для небольших запросов, таких как простой UPDATE, INSERT, DELETE или SELECT, планирование файберов справедливо: обработка запроса, планирование записи на диск и передача управления файберу, обслуживающему следующего клиента, занимают мало времени.

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