Spec-Zone.ru › Ruby 3

класс 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 была введена концепция неблокирующего волокна. Неблокирующее волокно, при достижении любой потенциально блокирующей операции (например, ожидания, ожидания другого процесса, ожидания готовности данных ввода/вывода), вместо простого зависания и блокировки всей выполнения в потоке, уступает управление другим волокнам и позволяет планировщику обрабатывать ожидание и пробуждение (возобновление) волокна, когда оно может продолжить выполнение.

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

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

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

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

blocking? → false или число Показать исходный код
static VALUE
rb_f_fiber_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 блокирующее, метод, в отличие от обычных предикатов, возвращает число блокирующих волокон, выполняющихся в настоящее время (TBD: всегда 1?).

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

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

current() → волокно Показать исходный код
static VALUE
rb_fiber_s_current(VALUE klass)
{
    return rb_fiber_current();
}

Возвращает текущее волокно. Вам необходимо require 'fiber' перед использованием этого метода. Если вы не работаете в контексте волокна, этот метод вернёт корневое волокно.

new(blocking: false) { |*args| ... } → волокно Показать исходный код
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 становится неблокирующим (см. раздел «Неблокирующие волокна» в документации класса).

schedule { |*args| ... } → волокно Показать исходный код
static VALUE
rb_f_fiber(int argc, VALUE *argv, VALUE obj)
{
    return rb_f_fiber_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::SchedulerInterface#fiber. Ruby не накладывает никаких ограничений на поведение этого метода.

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

scheduler → объект или null Показать исходный код
static VALUE
rb_fiber_scheduler(VALUE klass)
{
    return rb_scheduler_get();
}

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

set_scheduler(scheduler) → scheduler Показать исходный код
static VALUE
rb_fiber_set_scheduler(VALUE klass, VALUE scheduler)
{
    // if (rb_scheduler_get() != Qnil) {
    //     rb_raise(rb_eFiberError, "Scheduler is already defined!");
    // }

    return rb_scheduler_set(scheduler);
}

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

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

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

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 или false Показать исходный код
VALUE
rb_fiber_alive_p(VALUE fiber_value)
{
    return FIBER_TERMINATED_P(fiber_ptr(fiber_value)) ? Qfalse : Qtrue;
}

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

backtrace → массив Показать исходный код
backtrace(начало) → массив
backtrace(начало, количество) → массив
backtrace(начало..конец) → массив
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 → массив Показать исходный код
backtrace_locations(начало) → массив
backtrace_locations(начало, количество) → массив
backtrace_locations(начало..конец) → массив
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 (fiber_ptr(fiber)->blocking == 0) ? Qfalse : Qtrue;
}

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

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

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

Возвращает строку информации о волокне.

Псевдоним для: to_s
raise → obj Показать исходный код
raise(строка) → obj
raise(исключение [, строка [, массив]]) → obj
static VALUE
rb_fiber_raise(int argc, VALUE *argv, VALUE fiber_value)
{
    rb_fiber_t *fiber = fiber_ptr(fiber_value);
    VALUE exc = rb_make_exception(argc, argv);
    if (RTEST(fiber->resuming_fiber)) {
        rb_raise(rb_eFiberError, "attempt to raise a resuming fiber");
    }
    else if (FIBER_SUSPENDED_P(fiber) && !fiber->yielding) {
        return rb_fiber_transfer_kw(fiber_value, -1, &exc, RB_NO_KEYWORDS);
    }
    else {
        return rb_fiber_resume_kw(fiber_value, -1, &exc, RB_NO_KEYWORDS);
    }
}

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

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

resume(аргументы, ...) → 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.

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 (RTEST(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(аргументы, ...) → obj Показать исходный код
static VALUE
rb_fiber_m_transfer(int argc, VALUE *argv, VALUE fiber_value)
{
    return rb_fiber_transfer_kw(fiber_value, argc, argv, rb_keyword_given_p());
}

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

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

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

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

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

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

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

Пример:

require 'fiber'

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–2020 Yukihiro Matsumoto
Licensed under the Ruby License.
Ruby Standard Library © contributors
Licensed under their own licenses.

Spec-Zone.ru

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