Вопрос

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

Это было полезно?

Решение

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

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

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

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

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

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

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

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

    Как МРТ, C Threads может фактически работают параллельно Ruby Threads.

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

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

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

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

  6. Руби.НЕТ также реализует рубиновые нити в виде потоков CLR. Обновлять: Ruby.NET мертв.

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

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

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

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

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

  10. Маглев это реализация Ruby для Gemstone/s Smalltalk VM.У меня нет информации, какая модель потоков использует Gemstone/S, какая модель потоков Maglev использует или даже если потоки даже реализованы (вероятно, нет).

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

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

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

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

Другие советы

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

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

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

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




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

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

Это результат простая программа Ruby, использующая 3 потока используя Руби 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. (настоящие потоки «на уровне ОС») и есть нет ГВЛ

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

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

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

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 (08.05.2014) [x86_64-linux-gnu]

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

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

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

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

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

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

Поскольку не удалось отредактировать этот ответ, добавьте сюда новый ответ.

Обновление (08.05.2017)

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

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

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

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top