Spec-Zone.ru › Ruby 3.3

класс Fiber

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

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

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

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

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

Например:

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.new с blocking: false (что является значением по умолчанию), и Fiber.scheduler должен быть установлен с помощью Fiber.set_scheduler. Если Fiber.scheduler не установлен в текущем потоке, поведение блокирующих и неблокирующих волокон идентично.

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

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

Методы публичного класса

Fiber[ключ] → значение Показать исходный код
static VALUE
rb_fiber_storage_aref(VALUE class, VALUE key)
{
    Check_Type(key, T_SYMBOL);

    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[ключ] = значение Показать исходный код
static VALUE
rb_fiber_storage_aset(VALUE class, VALUE key, VALUE value)
{
    Check_Type(key, T_SYMBOL);

    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{|волокно| ...} → результат Показать исходный код
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 или 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 → волокно Показать исходный код
static VALUE
rb_fiber_s_current(VALUE klass)
{
    return rb_fiber_current();
}

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

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

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

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

Если 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| ... } → волокно Показать исходный код
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 → объект или nil Показать исходный код
static VALUE
rb_fiber_s_scheduler(VALUE klass)
{
    return rb_fiber_scheduler_get();
}

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

set_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(аргументы, ...) → объект Показать исходный код
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 → массив Показать исходный код
backtrace(start) → массив
backtrace(start, count) → массив
backtrace(start..end) → массив
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(start) → массив
backtrace_locations(start, count) → массив
backtrace_locations(start..end) → массив
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.

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 → хэш (дубликат) Показать исходный код
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 = хэш Показать исходный код
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.

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

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

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

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

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

Пример:

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

Spec-Zone.ru

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