Spec-Zone.ru › Ruby 3.4

класс Fiber

Родитель:
Объект

Волокна — это примитивы для реализации лёгкой кооперативной конкурентности в Ruby. По сути, это способ создания блоков кода, которые могут приостанавливаться и возобновляться, очень похожие на потоки. Главное отличие заключается в том, что они никогда не прерываются, а планирование должно выполняться программистом, а не виртуальной машиной.

В отличие от других моделей бесстековой лёгкой конкурентности, каждое волокно имеет стек. Это позволяет волокну приостанавливаться из глубоко вложенных вызовов функций внутри блока волокна. См. руководство ruby(1) для настройки размера стека волокна(ов).

Когда волокно создаётся, оно не запускается автоматически. Вместо этого его необходимо явно запустить, используя метод Fiber#resume. Код внутри волокна может уступить управление, вызвав Fiber.yield, в этом случае он передаёт управление обратно вызывающей стороне (вызывающей стороне Fiber#resume).

При передаче управления или завершении Fiber возвращает значение последнего выполненного выражения

Например:

fiber = Fiber.new do
  Fiber.yield 1
  2
end

puts fiber.resume
puts fiber.resume
puts fiber.resume

производит

1
2
FiberError: dead fiber called

Метод Fiber#resume принимает произвольное количество параметров. Если это первый вызов resume, то они будут переданы как аргументы блока. В противном случае они будут возвращаемым значением вызова Fiber.yield

Пример:

fiber = Fiber.new do |first|
  second = Fiber.yield first + 2
end

puts fiber.resume 10
puts fiber.resume 1_000_000
puts fiber.resume "The fiber will be dead before I can cause trouble"

производит

12
1000000
FiberError: dead fiber called

Неблокирующие волокна

Концепция неблокирующего волокна была введена в Ruby 3.0. Неблокирующее волокно, при достижении операции, которая обычно блокирует волокно (например, sleep, или ожидание другого процесса или ввода/вывода), передаёт управление другим волокнам и позволяет планировщику обрабатывать блокирование и пробуждение (возобновление) этого волокна, когда оно сможет продолжить работу.

Для того, чтобы Fiber работало как неблокирующее, его необходимо создать в Fiber.new с blocking: false (что является значением по умолчанию), и Fiber.scheduler должен быть установлен с помощью Fiber.set_scheduler. Если Fiber.scheduler не установлен в текущем потоке, поведение блокирующих и неблокирующих волокон идентично.

Ruby не предоставляет класс планировщика: ожидается, что он будет реализован пользователем и соответствовать Fiber::Scheduler.

Также есть метод Fiber.schedule, который должен немедленно выполнить заданный блок в неблокирующем режиме. Его фактическая реализация зависит от планировщика.

Публичные методы класса

Fiber[key] → value
Source
static VALUE
rb_fiber_storage_aref(VALUE class, VALUE key)
{
    key = rb_to_symbol(key);

    VALUE storage = fiber_storage_get(fiber_current(), FALSE);
    if (storage == Qnil) return Qnil;

    return rb_hash_aref(storage, key);
}

Возвращает значение переменной хранилища волокон, идентифицированной по key.

key должен быть символом, а значение устанавливается с помощью Fiber#[]= или Fiber#store.

См. также Fiber::[]=.

Fiber[key] = value
Source
static VALUE
rb_fiber_storage_aset(VALUE class, VALUE key, VALUE value)
{
    key = rb_to_symbol(key);

    VALUE storage = fiber_storage_get(fiber_current(), value != Qnil);
    if (storage == Qnil) return Qnil;

    if (value == Qnil) {
        return rb_hash_delete(storage, key);
    }
    else {
        return rb_hash_aset(storage, key, value);
    }
}

Присваивает value переменной хранилища волокон, идентифицированной по key. Переменная создается, если она не существует.

key должен быть Symbol, иначе возникает TypeError.

См. также Fiber::[].

blocking{|fiber| ...} → result
Source
VALUE
rb_fiber_blocking(VALUE class)
{
    VALUE fiber_value = rb_fiber_current();
    rb_fiber_t *fiber = fiber_ptr(fiber_value);

    // If we are already blocking, this is essentially a no-op:
    if (fiber->blocking) {
        return rb_yield(fiber_value);
    }
    else {
        return rb_ensure(fiber_blocking_yield, fiber_value, fiber_blocking_ensure, fiber_value);
    }
}

Заставляет волокно быть блокирующим на время блока. Возвращает результат блока.

См. раздел «Неблокирующие волокна» в документации класса для получения подробной информации.

blocking? → false or 1
Source
static VALUE
rb_fiber_s_blocking_p(VALUE klass)
{
    rb_thread_t *thread = GET_THREAD();
    unsigned blocking = thread->blocking;

    if (blocking == 0)
        return Qfalse;

    return INT2NUM(blocking);
}

Возвращает false, если текущее волокно неблокирующее. Fiber является неблокирующим, если он был создан путем передачи blocking: false в Fiber.new или с помощью Fiber.schedule.

Если текущее Fiber блокируется, метод возвращает 1. Будущие разработки могут допускать ситуации, когда могут возвращаться большие целые числа.

Обратите внимание, что даже если метод возвращает false, Fiber ведет себя по-разному только если Fiber.scheduler установлен в текущем потоке.

См. раздел «Неблокирующие волокна» в документации класса для получения подробной информации.

current → fiber
Source
static VALUE
rb_fiber_s_current(VALUE klass)
{
    return rb_fiber_current();
}

Возвращает текущее волокно. Если вы не работаете в контексте волокна, этот метод вернет корневое волокно.

current_scheduler → obj or nil
Source
static VALUE
rb_fiber_current_scheduler(VALUE klass)
{
    return rb_fiber_scheduler_current();
}

Возвращает планировщик Fiber, который был последний раз установлен для текущего потока с помощью Fiber.set_scheduler, если и только если текущее волокно неблокирующее.

new(blocking: false, storage: true) { |*args| ... } → fiber
Source
static VALUE
rb_fiber_initialize(int argc, VALUE* argv, VALUE self)
{
    return rb_fiber_initialize_kw(argc, argv, self, rb_keyword_given_p());
}

Создает новое Fiber. Первоначально волокно не запущено и может быть возобновлено с помощью resume. Аргументы к первому вызову resume будут переданы в блок:

f = Fiber.new do |initial|
   current = initial
   loop do
     puts "current: #{current.inspect}"
     current = Fiber.yield
   end
end
f.resume(100)     # prints: current: 100
f.resume(1, 2, 3) # prints: current: [1, 2, 3]
f.resume          # prints: current: nil
# ... and so on ...

Если blocking: false передается в Fiber.new, и текущий поток имеет определенный Fiber.scheduler, Fiber становится неблокирующим (см. раздел «Неблокирующие волокна» в документации класса).

Если storage не указан, по умолчанию используется копия хранилища из текущего волокна. Это то же самое, что и указание storage: true.

Fiber[:x] = 1
Fiber.new do
  Fiber[:x] # => 1
  Fiber[:x] = 2
end.resume
Fiber[:x] # => 1

Если заданный storage равен nil, эта функция будет лениво инициализировать внутреннее хранилище, которое начинается как пустой хэш.

Fiber[:x] = "Hello World"
Fiber.new(storage: nil) do
  Fiber[:x] # nil
end

В противном случае заданный storage используется в качестве хранилища нового волокна, и он должен быть экземпляром Hash.

Явное использование storage: true в настоящее время является экспериментальным и может измениться в будущем.

schedule { |*args| ... } → fiber
Source
static VALUE
rb_fiber_s_schedule(int argc, VALUE *argv, VALUE obj)
{
    return rb_fiber_s_schedule_kw(argc, argv, rb_keyword_given_p());
}

Ожидается, что метод немедленно запустит предоставленный блок кода в отдельном неблокирующем волокне.

puts "Go to sleep!"

Fiber.set_scheduler(MyScheduler.new)

Fiber.schedule do
  puts "Going to sleep"
  sleep(1)
  puts "I slept well"
end

puts "Wakey-wakey, sleepyhead"

Предполагая, что MyScheduler правильно реализован, эта программа выдаст:

Go to sleep!
Going to sleep
Wakey-wakey, sleepyhead
...1 sec pause here...
I slept well

…например, при первой блокирующей операции внутри Fiber (sleep(1)), управление передается во внешний код (главное волокно), и в конце этого выполнения планировщик позаботится о правильном возобновлении всех заблокированных волокон.

Обратите внимание, что описанное выше поведение — это то, как ожидается, что будет вести себя метод, фактическое поведение зависит от реализации текущего планировщика метода Fiber::Scheduler#fiber. Ruby не требует, чтобы этот метод вел себя каким-либо определенным образом.

Если планировщик не установлен, метод вызывает RuntimeError (No scheduler is available!).

scheduler → obj or nil
Source
static VALUE
rb_fiber_s_scheduler(VALUE klass)
{
    return rb_fiber_scheduler_get();
}

Возвращает планировщик Fiber, который был последний раз установлен для текущего потока с помощью Fiber.set_scheduler. Возвращает nil, если планировщик не установлен (что является значением по умолчанию), и поведение неблокирующих волокон такое же, как и блокирующих. (см. раздел «Неблокирующие волокна» в документации класса для получения подробной информации о концепции планировщика).

set_scheduler(scheduler) → scheduler
Source
static VALUE
rb_fiber_set_scheduler(VALUE klass, VALUE scheduler)
{
    return rb_fiber_scheduler_set(scheduler);
}

Устанавливает планировщик Fiber для текущего потока. Если планировщик установлен, неблокирующие волокна (созданные Fiber.new с blocking: false, или Fiber.schedule) вызывают методы хуков планировщика при потенциально блокирующих операциях, и текущий поток будет вызывать метод close планировщика при завершении (позволяя планировщику правильно управлять всеми незавершенными волокнами).

scheduler может быть объектом любого класса, соответствующего Fiber::Scheduler. Его реализация зависит от пользователя.

См. также раздел «Неблокирующие волокна» в документации класса.

yield(args, ...) → obj
Source
static VALUE
rb_fiber_s_yield(int argc, VALUE *argv, VALUE klass)
{
    return rb_fiber_yield_kw(argc, argv, rb_keyword_given_p());
}

Возвращает управление в контекст, который возобновил волокно, передавая все аргументы, которые были переданы ему. Волокно возобновит обработку в этом месте, когда resume будет вызвано в следующий раз. Любые аргументы, переданные в следующий resume, будут значением, которое вычисляется этим выражением Fiber.yield.

Публичные методы экземпляра

alive? → true или false
Исходный код
VALUE
rb_fiber_alive_p(VALUE fiber_value)
{
    return RBOOL(!FIBER_TERMINATED_P(fiber_ptr(fiber_value)));
}

Возвращает true, если волокно всё ещё может быть возобновлено (или передано). После завершения выполнения блока волокна этот метод всегда будет возвращать false.

backtrace → array
backtrace(start) → array
backtrace(start, count) → array
backtrace(start..end) → array
Исходный код
static VALUE
rb_fiber_backtrace(int argc, VALUE *argv, VALUE fiber)
{
    return rb_vm_backtrace(argc, argv, &fiber_ptr(fiber)->cont.saved_ec);
}

Возвращает текущий стек выполнения волокна. start, count и end позволяют выбрать только части трассировки.

def level3
  Fiber.yield
end

def level2
  level3
end

def level1
  level2
end

f = Fiber.new { level1 }

# It is empty before the fiber started
f.backtrace
#=> []

f.resume

f.backtrace
#=> ["test.rb:2:in `yield'", "test.rb:2:in `level3'", "test.rb:6:in `level2'", "test.rb:10:in `level1'", "test.rb:13:in `block in <main>'"]
p f.backtrace(1) # start from the item 1
#=> ["test.rb:2:in `level3'", "test.rb:6:in `level2'", "test.rb:10:in `level1'", "test.rb:13:in `block in <main>'"]
p f.backtrace(2, 2) # start from item 2, take 2
#=> ["test.rb:6:in `level2'", "test.rb:10:in `level1'"]
p f.backtrace(1..3) # take items from 1 to 3
#=> ["test.rb:2:in `level3'", "test.rb:6:in `level2'", "test.rb:10:in `level1'"]

f.resume

# It is nil after the fiber is finished
f.backtrace
#=> nil
backtrace_locations → array
backtrace_locations(start) → array
backtrace_locations(start, count) → array
backtrace_locations(start..end) → array
Исходный код
static VALUE
rb_fiber_backtrace_locations(int argc, VALUE *argv, VALUE fiber)
{
    return rb_vm_backtrace_locations(argc, argv, &fiber_ptr(fiber)->cont.saved_ec);
}

Как backtrace, но возвращает каждую строку стека выполнения как Thread::Backtrace::Location. Принимает те же аргументы, что и backtrace.

f = Fiber.new { Fiber.yield }
f.resume
loc = f.backtrace_locations.first
loc.label  #=> "yield"
loc.path   #=> "test.rb"
loc.lineno #=> 1
blocking? → true или false
Исходный код
VALUE
rb_fiber_blocking_p(VALUE fiber)
{
    return RBOOL(fiber_ptr(fiber)->blocking);
}

Возвращает true, если fiber блокируется и false в противном случае. Fiber не блокируется, если он был создан путем передачи blocking: false в Fiber.new или через Fiber.schedule.

Обратите внимание, что даже если метод возвращает false, волокно ведет себя по-разному только в том случае, если Fiber.scheduler установлен в текущем потоке.

См. раздел «Неблокирующие волокна» в документации класса для получения подробной информации.

inspect ()
Псевдоним для: to_s
kill → nil
Исходный код
static VALUE
rb_fiber_m_kill(VALUE self)
{
    rb_fiber_t *fiber = fiber_ptr(self);

    if (fiber->killed) return Qfalse;
    fiber->killed = 1;

    if (fiber->status == FIBER_CREATED) {
        fiber->status = FIBER_TERMINATED;
    }
    else if (fiber->status != FIBER_TERMINATED) {
        if (fiber_current() == fiber) {
            fiber_check_killed(fiber);
        }
        else {
            fiber_raise(fiber_ptr(self), Qnil);
        }
    }

    return self;
}

Завершает волокно, вызывая неперехватываемое исключение. Оно завершает только данное волокно, а не другое волокно, возвращая nil другому волокну, если это волокно вызывало resume или transfer.

Fiber#kill прерывает другое волокно только тогда, когда оно находится в Fiber.yield. При вызове в текущем волокне оно вызывает это исключение в месте вызова Fiber#kill.

Если волокно не было запущено, переходит непосредственно в завершенное состояние.

Если волокно уже завершено, ничего не делает.

Вызывает FiberError, если вызывается для волокна, принадлежащего другому потоку.

raise → obj
raise(string) → obj
raise(exception [, string [, array]]) → obj
Исходный код
static VALUE
rb_fiber_m_raise(int argc, VALUE *argv, VALUE self)
{
    return rb_fiber_raise(self, argc, argv);
}

Вызывает исключение в волокне в точке, в которой был вызван последний Fiber.yield. Если волокно не было запущено или уже выполнено до конца, вызывает FiberError. Если волокно приостановлено, оно возобновляется. Если оно переносится, оно переносится внутрь. Но если оно возобновляется, вызывает FiberError.

Без аргументов вызывает RuntimeError. С одним аргументом String вызывает RuntimeError со строкой в качестве сообщения. В противном случае первый параметр должен быть именем класса Exception (или объекта, который возвращает объект Exception при отправке сообщения exception). Необязательный второй параметр устанавливает сообщение, связанное с исключением, а третий параметр — это массив информации о вызове. Исключения перехватываются предложением rescue блоков begin...end.

Вызывает FiberError, если вызывается для Fiber, принадлежащего другому Thread.

См. Kernel#raise для получения дополнительной информации.

resume(args, ...) → obj
Исходный код
static VALUE
rb_fiber_m_resume(int argc, VALUE *argv, VALUE fiber)
{
    return rb_fiber_resume_kw(fiber, argc, argv, rb_keyword_given_p());
}

Возобновляет волокно с точки, в которой был вызван последний Fiber.yield, или начинает его выполнение, если это первый вызов resume. Аргументы, переданные в resume, будут значением выражения Fiber.yield или будут переданы в качестве параметров блока в блок волокна, если это первый resume.

В качестве альтернативы, когда вызывается resume, он вычисляется в аргументы, переданные в следующее выражение Fiber.yield внутри блока волокна, или в значение блока, если он выполняется до завершения без какого-либо Fiber.yield

storage → hash (dup)
Исходный код
static VALUE
rb_fiber_storage_get(VALUE self)
{
    storage_access_must_be_from_same_fiber(self);

    VALUE storage = fiber_storage_get(fiber_ptr(self), FALSE);

    if (storage == Qnil) {
        return Qnil;
    }
    else {
        return rb_obj_dup(storage);
    }
}

Возвращает копию хэш-таблицы хранилища для волокна. Метод может быть вызван только для Fiber.current.

storage = hash
Исходный код
static VALUE
rb_fiber_storage_set(VALUE self, VALUE value)
{
    if (rb_warning_category_enabled_p(RB_WARN_CATEGORY_EXPERIMENTAL)) {
        rb_category_warn(RB_WARN_CATEGORY_EXPERIMENTAL,
          "Fiber#storage= is experimental and may be removed in the future!");
    }

    storage_access_must_be_from_same_fiber(self);
    fiber_storage_validate(value);

    fiber_ptr(self)->cont.saved_ec.storage = rb_obj_dup(value);
    return value;
}

Устанавливает хэш-таблицу хранилища для волокна. Эта функция экспериментальная и может измениться в будущем. Метод может быть вызван только для Fiber.current.

Следует осторожно использовать этот метод, так как вы можете непреднамеренно очистить важное состояние хранилища волокна. В основном следует предпочесть назначение конкретных ключей в хранилище с помощью Fiber::[]=.

Вы также можете использовать Fiber.new(storage: nil) для создания волокна с пустым хранилищем.

Пример:

while request = request_queue.pop
  # Reset the per-request state:
  Fiber.current.storage = nil
  handle_request(request)
end
to_s ()
Исходный код
static VALUE
fiber_to_s(VALUE fiber_value)
{
    const rb_fiber_t *fiber = fiber_ptr(fiber_value);
    const rb_proc_t *proc;
    char status_info[0x20];

    if (fiber->resuming_fiber) {
        snprintf(status_info, 0x20, " (%s by resuming)", fiber_status_name(fiber->status));
    }
    else {
        snprintf(status_info, 0x20, " (%s)", fiber_status_name(fiber->status));
    }

    if (!rb_obj_is_proc(fiber->first_proc)) {
        VALUE str = rb_any_to_s(fiber_value);
        strlcat(status_info, ">", sizeof(status_info));
        rb_str_set_len(str, RSTRING_LEN(str)-1);
        rb_str_cat_cstr(str, status_info);
        return str;
    }
    GetProcPtr(fiber->first_proc, proc);
    return rb_block_to_s(fiber_value, &proc->block, status_info);
}
Также известен как: inspect
transfer(args, ...) → obj
Исходный код
static VALUE
rb_fiber_m_transfer(int argc, VALUE *argv, VALUE self)
{
    return rb_fiber_transfer_kw(self, argc, argv, rb_keyword_given_p());
}

Передать управление другой волокне, возобновив его с точки остановки или запустив, если оно раньше не возобновлялось. Вызывающее волокно будет приостановлено так же, как при вызове Fiber.yield.

Волокно, которое получает вызов transfer, обрабатывает его как вызов resume. Аргументы, переданные в transfer, обрабатываются так же, как те, которые передаются в resume.

Два способа передачи управления волокну (один — resume и Fiber::yield, другой — transfer к волокну и от него) не могут быть свободно смешаны.

  • Если жизненный цикл волокна начался с transfer, оно никогда не сможет уступить или быть возобновлено, только завершиться или передать управление обратно. (При этом оно всё ещё может возобновить другие волокна, которые разрешено возобновлять.)

  • Если жизненный цикл волокна начался с resume, оно может уступить или передать управление другому Fiber, но может получить управление обратно только так, как оно было отдано: если оно передавало управление, то его можно только передать обратно, а если оно уступило, то его можно только возобновить. После этого оно снова может передавать или уступать управление.

Если эти правила нарушены, возбуждается FiberError.

Для индивидуального дизайна Fiber, yield/resume проще в использовании (волокно просто отдает управление, ему не нужно думать, кому оно отдаётся), в то время как transfer более гибкий для сложных случаев, позволяя строить произвольные графики зависимых друг от друга волокон.

Пример:

manager = nil # For local var to be visible inside worker block

# This fiber would be started with transfer
# It can't yield, and can't be resumed
worker = Fiber.new { |work|
  puts "Worker: starts"
  puts "Worker: Performed #{work.inspect}, transferring back"
  # Fiber.yield     # this would raise FiberError: attempt to yield on a not resumed fiber
  # manager.resume  # this would raise FiberError: attempt to resume a resumed fiber (double resume)
  manager.transfer(work.capitalize)
}

# This fiber would be started with resume
# It can yield or transfer, and can be transferred
# back or resumed
manager = Fiber.new {
  puts "Manager: starts"
  puts "Manager: transferring 'something' to worker"
  result = worker.transfer('something')
  puts "Manager: worker returned #{result.inspect}"
  # worker.resume    # this would raise FiberError: attempt to resume a transferring fiber
  Fiber.yield        # this is OK, the fiber transferred from and to, now it can yield
  puts "Manager: finished"
}

puts "Starting the manager"
manager.resume
puts "Resuming the manager"
# manager.transfer  # this would raise FiberError: attempt to transfer to a yielding fiber
manager.resume

приводит к

Starting the manager
Manager: starts
Manager: transferring 'something' to worker
Worker: starts
Worker: Performed "something", transferring back
Manager: worker returned "Something"
Resuming the manager
Manager: finished

Ruby Core © 1993–2024 Yukihiro Matsumoto
Licensed under the Ruby License.
Ruby Standard Library © contributors
Licensed under their own licenses.

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API