У ruby ​​есть реальная многопоточность?


295

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

Ответы:


612

Обновлено с комментариями Jörg в сентябре 2011 года

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

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

  1. Первая реализация на самом деле не имеет имени, что делает его довольно неудобным для обращения к нему и действительно раздражает и сбивает с толку. Чаще всего его называют «Ruby», что еще более раздражает и сбивает с толку, чем отсутствие имени, поскольку приводит к бесконечной путанице между функциями языка программирования Ruby и конкретной реализацией Ruby.

    Его также иногда называют «MRI» (для «реализации Ruby от Matz»), CRuby или MatzRuby.

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

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

  2. Вторая реализация - YARV (сокращение от «Another Another Ruby VM»). YARV реализует потоки Ruby как потоки POSIX или Windows NT , однако использует глобальную блокировку интерпретатора (GIL), чтобы гарантировать, что в любой момент времени может быть запланирован только один поток Ruby.

    Как и MRI, потоки C могут работать параллельно с потоками Ruby.

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

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

  4. XRuby также реализует Ruby Threads как потоки JVM . Обновление : XRuby мертв.

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

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

  7. Rubinius реализует Ruby Threads как зеленые потоки в своей виртуальной машине . Точнее: виртуальная машина Rubinius экспортирует очень легкую, очень гибкую конструкцию потока управления параллелизмом / параллелизмом / нелокальным потоком, называемую « задачей », и все другие конструкции параллелизма (темы в этом обсуждении, но также продолжения , актеры и другие вещи ) реализованы в чистом Ruby, используя Задачи.

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

    Обновление : информация о Рубиниусе в этом ответе - о дробовике ВМ, которого больше нет. «Новая» виртуальная машина C ++ не использует зеленые потоки, запланированные для нескольких виртуальных машин (т. Е. Стиль Erlang / BEAM), она использует более традиционную одиночную виртуальную машину с моделью с несколькими собственными потоками ОС, подобно той, которая используется, скажем, в CLR, Mono и в значительной степени каждый JVM.

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

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

  10. MagLev - это реализация Ruby для виртуальной машины GemStone / S Smalltalk . У меня нет информации о том, какую модель потоков использует GemStone / S, какую модель потоков использует MagLev или даже если потоки даже реализованы (вероятно, нет).

  11. HotRuby это не полный Рубин Осуществление своей собственной. Это реализация виртуальной машины YARV с байт-кодом в JavaScript. HotRuby не поддерживает потоки (пока?), И когда это произойдет, они не смогут работать параллельно, потому что JavaScript не поддерживает истинный параллелизм. Однако существует версия HotRuby для ActionScript, и ActionScript может фактически поддерживать параллелизм. Обновление : HotRuby мертв.

К сожалению, только две из этих 11 реализаций Ruby фактически готовы к работе: MRI и JRuby.

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

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


1
но использование форка прервет использование на jruby ... просто
скажу

1
Это отличный ответ. Однако это подвержено большому количеству гнили ссылок. Я не знаю, куда эти ресурсы могли переместиться, хотя.
BlackVegetarian

28

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

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


35
Неверно, что в Ruby 1.8 есть только зеленые потоки, в нескольких реализациях Ruby 1.8 есть нативные потоки: JRuby, XRuby, Ruby.NET и IronRuby. Волокна не позволяют создавать собственные потоки, они более легкие, чем потоки. Они на самом деле полу-сопрограммы, то есть они кооперативны.
Йорг Миттаг,

19
Я думаю, что из ответа Джоша вполне очевидно, что он имеет в виду Ruby 1.8 во время выполнения, он же MRI, а не Ruby 1.8 язык, когда он говорит Ruby 1.8.
Тео

@ Theo Также очевидно, что он ошибается в своих ответах. Волокна не являются способом создания собственных потоков, как уже упоминалось, они даже более легкие вещи, чем потоки, и у нынешнего cruby есть собственные потоки, но с GIL.
Зоопарк Foo Bar

8

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

  • МРТ не имеет, ЯРВ ближе.
  • JRuby и MacRuby есть.




Рубин имеет закрытия , как 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.



Та же программа, теперь с JRuby. Вы можете видеть три потока с состоянием 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 ( настоящими потоками уровня ОС ), а GVL отсутствует

(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 


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

(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 Under a Microscope .
Кроме того, Ruby Threads и Global Interpreter Lock в C в Omniref объясняют в исходном коде, почему потоки Ruby не работают параллельно.


Под RMI вы имеете в виду МРТ?
Мауреш Шривастава

4

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


3

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

jruby 1.5.6 (уровень исправления ruby ​​1.8.7 249) (2014-02-03 6586) (64-битный сервер OpenJDK VM 1.7.0_75) [amd64-java]

Второй с:

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

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

введите описание изображения здесь

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

1

Если вы используете MRI, то вы можете написать многопоточный код на C либо как расширение, либо используя гем ruby-inline.


1

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

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


JRuby - хороший вариант. Для параллельной обработки с использованием процессов мне нравится github.com/grosser/parallel Parallel.map(['a','b','c'], :in_processes=>3){...
user454322


1

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

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

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

  1. Opal - это компилятор исходного кода в Ruby to JavaScript. Он также имеет реализацию Ruby corelib, которая в настоящее время является очень активным develompent, и над ней работает много (внешнего интерфейса) фреймворка. и производство готово. Поскольку база основана на JavaScript, она не поддерживает параллельные потоки.

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

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.