Spec-Zone.ru › Ruby 3.4

class Fiber

Родительский класс:
Object

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

В отличие от других моделей легковесной многозадачности без стека, каждое волокно имеет собственный стек. Это позволяет приостанавливать волокно даже при глубоко вложенных вызовах функций внутри его блока. См. man-страницу 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 Показать исходный код
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 Показать исходный код
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 Показать исходный код
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 Показать исходный код
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 Показать исходный код
static VALUE
rb_fiber_s_current(VALUE klass)
{
    return rb_fiber_current();
}

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

current_scheduler → obj or nil Показать исходный код
static VALUE
rb_fiber_current_scheduler(VALUE klass)
{
    return rb_fiber_scheduler_current();
}

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

new(blocking: false, storage: true) { |*args| ... } → fiber Показать исходный код
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 ...

Если в Fiber.new передано blocking: false и для текущего потока определён 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 Показать исходный код
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 Показать исходный код
static VALUE
rb_fiber_s_scheduler(VALUE klass)
{
    return rb_fiber_scheduler_get();
}

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

set_scheduler(scheduler) → scheduler Показать исходный код
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 Показать исходный код
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 or 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 or 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;
}

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

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. Если волокно передало управление, оно возобновляется. Если оно передало управление посредством transfer, управление передаётся ему. Если же оно возобновляется, вызывается 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 в волокно и из него) нельзя свободно сочетать.

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

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

Если нарушить эти правила, будет вызвано исключение FiberError.

Для отдельного волокна Fiber использовать yield/resume проще (волокно Fiber просто передаёт управление, не задумываясь о том, кому именно оно передаётся), тогда как 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–2025 Yukihiro Matsumoto
Licensed under the Ruby License.
Ruby Standard Library © contributors
Licensed under their own licenses.

Spec-Zone.ru

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