У ruby есть настоящая многопоточность?

Я знаю о" кооперативной " резьбе ruby с помощью зеленые нити. Как я могу создать реальные потоки "уровня ОС" в своем приложении, чтобы использовать несколько ядер процессора для обработки?

9 ответов


обновлено с комментарием Йорга сентябрь 2011

Вы, кажется, путаете два очень разные вещи здесь: Язык программирования Ruby и конкретная потоковая модель one конкретная реализация языка программирования Ruby. Там в настоящее время вокруг 11 различных реализаций Ruby Язык программирования, с очень Различный и уникальный продевать нитку модели.

(к сожалению, только два из этих 11 реализации на самом деле готов к использованию в производстве, но к концу года это число вероятно, до четырех или пяти.) (обновление: сейчас 5: МРТ, JRuby, YARV (переводчик для Ruby 1.9), Rubinius и IronRuby).

  1. первая реализация фактически не имеет имени, которое делает его довольно неудобным ссылаться на него и действительно раздражает и запутанным. Его чаще всего называют "Рубином", который даже больше раздражает и запутаннее, чем не иметь имени, потому что это приводит к бесконечной путанице между особенностями Ruby Язык программирования и конкретная реализация Ruby.

    его также иногда называют " МРТ "(для " Рубина маца Реализация"), Крубы или Мацрубы.

    MRI реализует Ruby Threads как зеленые потоки внутри его переводчик. К сожалению, это не позволяет этим потокам чтобы быть запланированными параллельно, они могут запускать только один поток в время.

    однако любое количество потоков C (потоки POSIX и т. д.) может работать параллельно с рубиновым потоком, поэтому внешние библиотеки C или МРТ Расширения C, которые создают собственные потоки, все еще могут работать в параллельный.

  2. вторая реализация ЯРЬВ (сокращение от " пока Еще один Ruby VM"). YARV реализует рубиновые потоки как POSIX или Потоки Windows NT, однако, он использует глобальный переводчик Блокировка (GIL) для обеспечения только одна рубиновая нить может быть по расписанию в любое время.

    Как МРТ, C потоков can фактически выполняется параллельно потокам Ruby.

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

  3. JRuby реализует Ruby Threads как собственные потоки, где "собственные потоки" в случае JVM, очевидно, означает " JVM Нити." Джруби не накладывает на них никаких дополнительных блокировок. Так, действительно ли эти потоки могут работать параллельно, зависит от для JVM: некоторых JVM реализовать потоки JVM как потоки ОС и некоторых как зеленые нити. (Основные JVMs от Sun / Oracle используют исключительно потоки ОС начиная с JDK 1.3)

  4. модели xruby и реализует Ruby Потоки как потоки JVM. обновление: XRuby мертв.

  5. IronRuby реализует Ruby Threads как собственные потоки, где" собственные потоки " в случае CLR, очевидно, означает "CLR Threads". IronRuby не накладывает на них дополнительной блокировки, таким образом, они должны работать параллельно, пока ваша среда CLR поддерживает что.

  6. Ruby.NET также реализует Ruby Threads как CLR Нити. обновление: Ruby.NET мертв.

  7. Рубиниус реализует рубиновые потоки как зеленые потоки внутри своей виртуальной машины. Точнее: Рубиниус VM экспортирует очень легкий, очень гибкий параллелизм/параллелизм / нелокальная конструкция потока управления, называемая а"задание" и все другие конструкции параллелизма (потоки в это обсуждение, но и продолжения, актеры и другие вещи) реализованы в pure Ruby, используя задачи.

    Rubinius не может (в настоящее время) планировать потоки параллельно, однако, добавив, что это не слишком большая проблема: Рубиниус может уже запустите несколько экземпляров VM в нескольких потоках POSIX в параллель в рамках одного процесса Рубиниус. Поскольку потоки фактически реализованные в Ruby, они могут, как и любой другой Ruby объект, сериализуется и отправляется на другую виртуальную машину в другом В POSIX Нитка. (Это та же модель луча Эрланг VM использует для параллелизма SMP. Это уже реализована Рубиниус Актеры.)

    обновление: информация о Rubinius в этом ответе касается дробовика VM, которого больше не существует. "Новая" виртуальная машина C++ не использует зеленые потоки, запланированные для нескольких виртуальных машин (например, стиль Erlang/BEAM), она использует более традиционную одиночную виртуальную машину с несколькими собственными потоками ОС, как и одна работает, скажем, в CLR, Mono и почти в каждом JVM.

  8. MacRuby выступает начал как порт YARV на верхней части Objective - C среда выполнения и CoreFoundation и рамки Cocoa. Он теперь значительно отличается от YARV, но AFAIK в настоящее время еще разделяет ту же модель резьбы с YARV. обновление: MacRuby зависит от сборщика мусора яблок, который объявлен устаревшим и будет удален позже версии MacOSX, MacRuby-нежить.

  9. кардинал является реализацией Ruby для попугай Виртуальная Машина. Однако он еще не реализует потоки, когда это произойдет, он, вероятно, будет реализовывать их как попугай Нити. обновление: кардинал, кажется, очень не активен/мертв.

  10. MagLev является реализацией Ruby для Драгоценный Камень / S язык Smalltalk VM. У меня нет информации, какая модель threading GemStone / s использует, какую модель резьбы использует MagLev или даже если threads даже реализованы (вероятно, нет).

  11. HotRuby и не полная реализация Ruby его собственный. Это реализация байт-кода YARV VM в Яваскрипт. HotRuby не поддерживает потоки (пока?) и когда это нет, они не смогут работать параллельно, потому что JavaScript не имеет поддержки true параллелизм. Есть в ActionScript версия HotRuby, однако и ActionScript может на самом деле поддержите параллелизм. обновление: HotRuby мертв.

к сожалению, только две из этих 11 реализаций Ruby собственно производство-готово: МРТ и JRuby.

Итак, если вы хотите истинные параллельные потоки, JRuby в настоящее время ваш единственный выбор-не то, что это плохо: JRuby на самом деле быстрее чем МРТ, и, возможно, больше стабильный.

в противном случае "классическим" решением Ruby является использование процессов вместо нитей для параллелизма. Библиотека Ruby Core содержит Process модуль С Process.fork метод что делает его мертвым легко раскошелиться на другой Рубин процесс. Кроме того, стандартная библиотека Ruby содержит распределенный Рубин (dRuby / dRb) библиотека, которая позволяет Ruby код, который должен быть тривиально распределен между несколькими процессами, а не только на та же машина, но и по всей сети.


Ruby 1.8 имеет только зеленые потоки, нет никакого способа создать реальный поток "уровня ОС". Но ruby 1.9 будет иметь новую функцию под названием fibers, которая позволит вам создавать фактические потоки уровня ОС. К сожалению, Ruby 1.9 все еще находится в бета-версии, он должен быть стабильным через пару месяцев.

Другой альтернативой является использование JRuby. JRuby реализует потоки как на уровне ОС, в нем нет" зеленых потоков". Последняя версия JRuby-1.1.4 и эквивалентна Руби 1.8


это зависит от реализации:

  • RMI не имеет, Ярв ближе.
  • JRuby и MacRuby выступает уже.




Руби закрытие as Blocks, lambdas и Procs. Чтобы в полной мере воспользоваться закрытиями и несколькими ядрами в JRuby,исполнители Java пригодится, ибо я MacRuby выступает как очереди GCD.

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

это выход простая программа Ruby, которая использует 3 потока использование Ruby 2.1.0:

(jalcazar@mac ~)$ ps -M 69877
USER     PID   TT   %CPU STAT PRI     STIME     UTIME COMMAND
jalcazar 69877 s002    0.0 S    31T   0:00.01   0:00.04 /Users/jalcazar/.rvm/rubies/ruby-2.1.0/bin/ruby threads.rb
   69877         0.0 S    31T   0:00.01   0:00.00 
   69877        33.4 S    31T   0:00.01   0:08.73 
   69877        43.1 S    31T   0:00.01   0:08.73 
   69877        22.8 R    31T   0:00.01   0:08.65 

как вы можете видеть здесь, есть четыре потока ОС, однако только один с состоянием R работает. Это связано с ограничением в том, как потоки Ruby выполненный.



Та же программа, теперь с Джруби. Вы можете увидеть три потока с state R, что означает, что они выполняются параллельно.

(jalcazar@mac ~)$ ps -M 72286
USER     PID   TT   %CPU STAT PRI     STIME     UTIME COMMAND
jalcazar 72286 s002    0.0 S    31T   0:00.01   0:00.01 /Library/Java/JavaVirtualMachines/jdk1.7.0_25.jdk/Contents/Home/bin/java -Djdk.home= -Djruby.home=/Users/jalcazar/.rvm/rubies/jruby-1.7.10 -Djruby.script=jruby -Djruby.shell=/bin/sh -Djffi.boot.library.path=/Users/jalcazar/.rvm/rubies/jruby-1.7.10/lib/jni:/Users/jalcazar/.rvm/rubies/jruby-1.7.10/lib/jni/Darwin -Xss2048k -Dsun.java.command=org.jruby.Main -cp  -Xbootclasspath/a:/Users/jalcazar/.rvm/rubies/jruby-1.7.10/lib/jruby.jar -Xmx1924M -XX:PermSize=992m -Dfile.encoding=UTF-8 org/jruby/Main threads.rb
   72286         0.0 S    31T   0:00.00   0:00.00 
   72286         0.0 S    33T   0:00.00   0:00.00 
   72286         0.0 S    31T   0:00.09   0:02.34 
   72286         7.9 S    31T   0:00.15   0:04.63 
   72286         0.0 S    31T   0:00.00   0:00.00 
   72286         0.0 S    31T   0:00.00   0:00.00 
   72286         0.0 S    31T   0:00.00   0:00.00 
   72286         0.0 S    31T   0:00.04   0:01.68 
   72286         0.0 S    31T   0:00.03   0:01.54 
   72286         0.0 S    31T   0:00.00   0:00.00 
   72286         0.0 S    31T   0:00.01   0:00.01 
   72286         0.0 S    31T   0:00.00   0:00.01 
   72286         0.0 S    31T   0:00.00   0:00.03 
   72286        74.2 R    31T   0:09.21   0:37.73 
   72286        72.4 R    31T   0:09.24   0:37.71 
   72286        74.7 R    31T   0:09.24   0:37.80 


Та же программа, теперь с MacRuby выступает. Есть также три потока, работающие параллельно. Это потому что потоки MacRuby-это потоки POSIX (реальные потоки "уровня ОС") и нет ГВЛ

(jalcazar@mac ~)$ ps -M 38293
USER     PID   TT   %CPU STAT PRI     STIME     UTIME COMMAND
jalcazar 38293 s002    0.0 R     0T   0:00.02   0:00.10 /Users/jalcazar/.rvm/rubies/macruby-0.12/usr/bin/macruby threads.rb
   38293         0.0 S    33T   0:00.00   0:00.00 
   38293       100.0 R    31T   0:00.04   0:21.92 
   38293       100.0 R    31T   0:00.04   0:21.95 
   38293       100.0 R    31T   0:00.04   0:21.99 


Еще раз, та же программа, но теперь с хорошим старым RMI. Из-за того, что в этой реализации используются зеленые потоки, отображается только один поток

(jalcazar@mac ~)$ ps -M 70032
USER     PID   TT   %CPU STAT PRI     STIME     UTIME COMMAND
jalcazar 70032 s002  100.0 R    31T   0:00.08   0:26.62 /Users/jalcazar/.rvm/rubies/ruby-1.8.7-p374/bin/ruby threads.rb



Если вас интересует многопоточность Ruby, вы можете найти мой отчет отладка параллельных программ с помощью обработчиков вилка интересные.
Для более общего обзора внутренних компонентов Ruby Рубин под микроскопом хорошее чтение.
Кроме того,Ruby Threads и глобальная блокировка интерпретатора в C в Omniref объясняет в исходном коде, почему потоки Ruby не работают параллельно.


Как насчет использования drb? Это не настоящая многопоточность, а связь между несколькими процессами, но вы можете использовать ее сейчас в 1.8, и это довольно низкое трение.


Я позволю "системному монитору" ответить на этот вопрос. Я выполняю тот же код (ниже, который вычисляет простые числа) с 8 рубиновыми потоками, работающими на машине i7 (4 hyperthreaded-core) в обоих случаях... первый запуск с:

jruby 1.5.6 (ruby 1.8.7 patchlevel 249) (2014-02-03 6586) (OpenJDK 64-разрядный сервер VM 1.7.0_75) [amd64-java]

вторая-с:

ruby 2.1.2p95 (2014-05-08) [x86_64-linux-gnu]

интересно, процессор выше для потоков JRuby, но время до завершения немного короче для интерпретируемого Ruby. Это трудно сказать из графика, но второй (интерпретируемый Ruby) запуск использует около 1/2 процессоров (без гиперпотока?)

enter image description here

def eratosthenes(n)
  nums = [nil, nil, *2..n]
  (2..Math.sqrt(n)).each do |i|
    (i**2..n).step(i){|m| nums[m] = nil}  if nums[i]
  end
  nums.compact
end

MAX_PRIME=10000000
THREADS=8
threads = []

1.upto(THREADS) do |num|
  puts "Starting thread #{num}"
  threads[num]=Thread.new { eratosthenes MAX_PRIME }
end

1.upto(THREADS) do |num|
    threads[num].join
end

Если вы используете МРТ, то вы можете написать резьбовой код на C либо в качестве расширения, либо с помощью ruby-inline gem.


Если вам действительно нужен параллелизм в Ruby для системы производственного уровня (где вы не можете использовать бета-версию), процессы, вероятно, являются лучшей альтернативой.
Но, безусловно, стоит попробовать сначала под JRuby.

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


вот некоторая информация о Rinda, которая является Ruby-реализацией Linda (параллельная обработка и парадигма распределенных вычислений) http://charmalloc.blogspot.com/2009/12/linda-tuples-rinda-drb-parallel.html


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

обновление(2017-05-08)

эта статья очень старая, и информация не следовать текущим (2017) протектор, ниже некоторые дополнения:

  1. опалов является компилятором Ruby to JavaScript source-to-source. Он также имеет реализацию Ruby corelib, он в настоящее время очень активно развивается и существует много (frontend) framework на нем. и производство готово. Поскольку база на javascript, она не поддерживает параллельные потоки.

  2. truffleruby - это высокопроизводительная реализация языка программирования Ruby. Построенный на GraalVM Oracle Labs, TruffleRuby-это вилка JRuby, объединяющая его с кодом из проекта Rubinius, а также содержащая код из стандартной реализации Ruby, MRI, все еще живая разработка, не готовая к производству. Эта версия ruby кажется рожденной для производительность, я не знаю, поддерживает ли параллельные потоки, но я думаю, что это должно быть.