Czy ruby ​​ma prawdziwą wielowątkowość?


295

Wiem o „kooperacyjnym” nawlekaniu rubinu za pomocą zielonych nici . Jak mogę utworzyć w mojej aplikacji rzeczywiste wątki „na poziomie systemu operacyjnego”, aby korzystać z wielu rdzeni procesora do przetwarzania?

Odpowiedzi:


612

Zaktualizowano komentarzem Jörga z września 2011 r

Wydaje się, że mylisz tutaj dwie bardzo różne rzeczy: Ruby Programming Language i konkretny model wątków jednej konkretnej implementacji Ruby Programming Language. Obecnie istnieje około 11 różnych implementacji języka programowania Ruby, z bardzo różnymi i unikalnymi modelami wątków.

(Niestety, tylko dwie z tych 11 implementacji są faktycznie gotowe do użytku produkcyjnego, ale do końca roku liczba ta prawdopodobnie wzrośnie do czterech lub pięciu.) ( Aktualizacja : teraz jest 5: MRI, JRuby, YARV (tłumacz dla Ruby 1.9), Rubinius i IronRuby).

  1. Pierwsza implementacja nie ma nazwy, co sprawia, że ​​odwoływanie się do niej jest dość niewygodne i jest naprawdę denerwujące i mylące. Najczęściej jest określany jako „Ruby”, co jest nawet bardziej denerwujące i mylące niż brak nazwy, ponieważ prowadzi do niekończącego się pomieszania funkcji języka programowania Ruby z konkretną implementacją Ruby.

    Jest również czasami nazywany „MRI” (dla „Ruby Implementation Matza”), CRuby lub MatzRuby.

    MRI implementuje Ruby Threads jako Green Threads w swoim interpretatorze . Niestety nie pozwala na równoległe planowanie tych wątków, mogą one uruchamiać tylko jeden wątek na raz.

    Jednak dowolna liczba wątków C (wątki POSIX itp.) Może działać równolegle do wątku Ruby, więc zewnętrzne biblioteki C lub rozszerzenia C MRI, które tworzą własne wątki, mogą nadal działać równolegle.

  2. Druga implementacja to YARV (skrót od „Yet Another Ruby VM”). YARV implementuje Ruby Threads jako wątki POSIX lub Windows NT , jednak używa Global Interpreter Lock (GIL), aby zapewnić, że tylko jeden Ruby Thread może być faktycznie zaplanowany w tym samym czasie.

    Podobnie jak MRI, wątki C mogą faktycznie działać równolegle do wątków Ruby.

    W przyszłości możliwe jest, że GIL może zostać rozbity na bardziej drobnoziarniste zamki, umożliwiając w ten sposób coraz więcej kodów działających równolegle, ale jest to tak daleko, że nawet nie jest to jeszcze planowane .

  3. JRuby implementuje Ruby Threads jako Native Threads , gdzie „Native Threads” w przypadku JVM oczywiście oznaczają „JVM Threads”. JRuby nie nakłada na nich żadnych dodatkowych blokad. Zatem to, czy te wątki mogą faktycznie działać równolegle, zależy od maszyny JVM: niektóre maszyny JVM implementują Wątki JVM jako Wątki systemu operacyjnego, a niektóre jako Zielone Wątki. (Główne maszyny JVM od Sun / Oracle używają wyłącznie wątków systemu operacyjnego od JDK 1.3)

  4. XRuby implementuje także Ruby Threads jako JVM Threads . Aktualizacja : XRuby nie żyje.

  5. IronRuby implementuje Ruby Threads jako Native Threads , gdzie „Native Threads” w przypadku CLR oczywiście oznaczają „CLR Threads”. IronRuby nie nakłada na nich żadnego dodatkowego blokowania, więc powinny działać równolegle, o ile CLR to obsługuje.

  6. Ruby.NET implementuje także Ruby Threads jako CLR Threads . Aktualizacja: Ruby.NET nie żyje.

  7. Rubinius implementuje Ruby Threads jako Green Threads w swojej maszynie wirtualnej . Dokładniej: maszyna wirtualna Rubinius eksportuje bardzo lekką, bardzo elastyczną konstrukcję współbieżności / równoległości / nielokalnego sterowania przepływem, zwaną „ Zadaniem ”, i wszystkie inne konstrukcje współbieżności (Wątki w tej dyskusji, ale także Kontynuacje , Aktorzy i inne rzeczy ) są implementowane w czystym języku Ruby, przy użyciu Zadań.

    Rubinius nie może (obecnie) planować wątków równolegle, jednak dodanie tego nie stanowi większego problemu: Rubinius może już uruchamiać kilka wystąpień maszyn wirtualnych w kilku wątkach POSIX równolegle , w ramach jednego procesu Rubiniusa. Ponieważ wątki są faktycznie zaimplementowane w Ruby, mogą one, podobnie jak każdy inny obiekt Ruby, zostać serializowane i wysłane na inną maszynę wirtualną w innym wątku POSIX. (To ten sam model, którego używa BEAM Erlang VM dla współbieżności SMP. Jest już zaimplementowany dla aktorów Rubinius .)

    Aktualizacja : Informacje o Rubiniusie w tej odpowiedzi dotyczą maszyny wirtualnej Shotgun, która już nie istnieje. „Nowa” maszyna wirtualna C ++ nie korzysta z zielonych wątków zaplanowanych na wielu maszynach wirtualnych (tj. W stylu Erlang / BEAM), używa bardziej tradycyjnej pojedynczej maszyny wirtualnej z wieloma rodzimymi modelami wątków systemu operacyjnego, podobnie jak model powierzony przez CLR, Mono i prawie co JVM.

  8. MacRuby zaczynał jako port YARV na szczycie Objective-C Runtime oraz CoreFoundation i Cocoa Frameworks. Teraz znacznie odszedł od YARV, ale AFAIK nadal ma ten sam model gwintowania z YARV . Aktualizacja: MacRuby zależy od zbieracza śmieci jabłek, który został uznany za przestarzały i zostanie usunięty w późniejszych wersjach MacOSX, MacRuby jest nieumarły.

  9. Cardinal jest implementacją Ruby dla wirtualnej maszyny Parrot . Jednak nie implementuje jeszcze wątków, ale kiedy to zrobi, prawdopodobnie zaimplementuje je jako wątki papugi . Aktualizacja : Kardynał wydaje się bardzo nieaktywny / martwy.

  10. MagLev to Rubinowa implementacja dla GemStone / S Smalltalk VM . Nie mam informacji, jakiego modelu wątkowego używa GemStone / S, jakiego modelu wątkowego używa MagLev, a nawet jeśli wątki są jeszcze zaimplementowane (prawdopodobnie nie).

  11. HotRuby nie jest własną pełną implementacją Ruby. Jest to implementacja kodu bajtowego maszyny wirtualnej YARV w JavaScript. HotRuby nie obsługuje wątków (jeszcze?), A gdy tak, nie będą mogły działać równolegle, ponieważ JavaScript nie obsługuje prawdziwej równoległości. Istnieje jednak wersja HotRuby w języku ActionScript, a ActionScript może faktycznie obsługiwać równoległość. Aktualizacja : HotRuby nie żyje.

Niestety, tylko dwie z tych 11 implementacji Ruby są w rzeczywistości gotowe do produkcji: MRI i JRuby.

Tak więc, jeśli chcesz prawdziwych równoległych wątków, JRuby jest obecnie twoim jedynym wyborem - nie jest to zły wybór: JRuby jest w rzeczywistości szybszy niż MRI i prawdopodobnie bardziej stabilny.

W przeciwnym razie „klasycznym” rozwiązaniem Rubiego jest użycie procesów zamiast wątków do równoległości. Biblioteka Ruby Core zawiera Processmoduł z Process.fork metodą, która sprawia, że ​​bardzo łatwo jest oddzielić kolejny proces Ruby. Ponadto standardowa biblioteka Ruby zawiera bibliotekę Distributed Ruby (dRuby / dRb) , która umożliwia trywialną dystrybucję kodu Ruby w wielu procesach, nie tylko na tym samym komputerze, ale również w sieci.


1
ale użycie widelca przerwie korzystanie z jruby ... tylko mówiąc
akostadinov

1
To świetna odpowiedź. Jest to jednak bardzo podatne na rozkład linków. Nie wiem jednak, gdzie te zasoby mogły się przenieść.
BlackVegetable

28

Ruby 1.8 ma tylko zielone wątki, nie ma sposobu na stworzenie prawdziwego wątku „na poziomie systemu operacyjnego”. Ale Ruby 1.9 będzie miał nową funkcję o nazwie fibre, która pozwoli ci tworzyć rzeczywiste wątki na poziomie systemu operacyjnego. Niestety, Ruby 1.9 jest wciąż w fazie beta, ma być stabilny za kilka miesięcy.

Inną alternatywą jest użycie JRuby. JRuby implementuje wątki jako teady na poziomie systemu operacyjnego, nie ma w nim „zielonych wątków”. Najnowsza wersja JRuby to 1.1.4 i jest odpowiednikiem Ruby 1.8


35
To nieprawda, że ​​Ruby 1.8 ma tylko zielone wątki, kilka implementacji Ruby 1.8 ma natywne wątki: JRuby, XRuby, Ruby.NET i IronRuby. Włókna nie pozwalają na tworzenie wątków natywnych, są one bardziej lekkie niż wątki. W rzeczywistości są półkorupami, tzn. Współpracują.
Jörg W Mittag,

19
Myślę, że to dość oczywiste z odpowiedzi Josha, że ​​ma na myśli środowisko wykonawcze Ruby 1.8, czyli MRI, a nie język Ruby 1.8, kiedy mówi Ruby 1.8.
Theo

@ Theo Oczywiste jest również, że w swoich odpowiedziach przeklina koncepcje. Włókna nie są sposobem tworzenia natywnych wątków, jak już wspomniano, są jeszcze bardziej lekkie niż wątki, a obecny cruby ma natywne wątki, ale z GIL.
Zoo Foo Bar

8

To zależy od wdrożenia:

  • MRI nie ma, YARV jest bliżej.
  • JRuby i MacRuby mają.




Ruby ma zamknięć jak Blocks, lambdasi Procs. Aby w pełni korzystać z zamknięć i wielu rdzeni w JRuby, przydatne są narzędzia wykonawcze Javy ; dla MacRuby lubię kolejki GCD .

Zauważ, że możliwość tworzenia prawdziwych wątków „na poziomie systemu operacyjnego” nie oznacza, że ​​możesz używać wielu rdzeni procesora do przetwarzania równoległego. Spójrz na poniższe przykłady.

To jest wynik prostego programu Ruby, który używa 3 wątków przy użyciu 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 

Jak widać tutaj, istnieją cztery wątki systemu operacyjnego, jednak działa tylko ten ze stanem R. Wynika to z ograniczenia implementacji wątków Ruby.



Ten sam program, teraz z JRuby. Możesz zobaczyć trzy wątki ze stanem R, co oznacza, że ​​działają równolegle.

(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 


Ten sam program, teraz z MacRuby. Istnieją również trzy wątki działające równolegle. Jest tak, ponieważ wątki MacRuby są wątkami POSIX ( prawdziwe wątki „na poziomie systemu operacyjnego” ) i nie ma 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 


Jeszcze raz ten sam program, ale teraz z dobrym starym MRI. Ze względu na fakt, że ta implementacja używa zielonych wątków, pojawia się tylko jeden wątek

(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



Jeśli interesuje Cię wielowątkowość Rubiego, mój raport może debugować programy równoległe za pomocą programów do obsługi wideł .
Bardziej ogólny przegląd wewnętrznych elementów Ruby to Ruby Under a Microscope .
Ponadto Ruby Threads i Global Interpreter Lock w C w Omniref wyjaśniają w kodzie źródłowym, dlaczego wątki Ruby nie działają równolegle.


Przez RMI masz na myśli MRI?
Mayuresh Srivastava

4

Co powiesz na używanie drb ? To nie jest prawdziwa wielowątkowość, ale komunikacja między kilkoma procesami, ale możesz go teraz używać w wersji 1.8 i ma dość niskie tarcie.


3

Pozwolę, aby „Monitor systemu” odpowiedział na to pytanie. Wykonuję ten sam kod (poniżej, który oblicza liczby pierwsze) z 8 wątkami Ruby działającymi na maszynie i7 (4 z rdzeniem hyperthreaded-core) w obu przypadkach ... pierwsze uruchomienie jest z:

jruby 1.5.6 (ruby 1.8.7 patchlevel 249) (2014-02-03 6586) (OpenJDK 64-Bit VM Server 1.7.0_75) [amd64-java]

Drugi dotyczy:

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

Co ciekawe, procesor jest wyższy dla wątków JRuby, ale czas na ukończenie jest nieco krótszy dla interpretowanej Ruby. Trudno powiedzieć na podstawie wykresu, ale drugi (zinterpretowany Ruby) proces wykorzystuje około 1/2 procesorów (bez hipertekstu?)

wprowadź opis zdjęcia tutaj

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

Jeśli używasz MRI, możesz napisać kod wątkowy w C albo jako rozszerzenie, albo używając klejnotu ruby-inline.


1

Jeśli naprawdę potrzebujesz równoległości w Ruby dla systemu na poziomie produkcji (w którym nie możesz zatrudnić wersji beta), procesy są prawdopodobnie lepszą alternatywą.
Ale zdecydowanie zdecydowanie warto najpierw wypróbować wątki w JRuby.

Również, jeśli jesteś zainteresowany przyszłością tworzenia wątków pod Ruby, ten artykuł może okazać się przydatny.


JRuby to dobra opcja. Do przetwarzania równoległego z wykorzystaniem procesów lubię github.com/grosser/parallel Parallel.map(['a','b','c'], :in_processes=>3){...
user454322


1

Ponieważ nie można edytować tej odpowiedzi, dodaj tutaj nową odpowiedź.

Aktualizacja (2017-05-08)

Ten artykuł jest bardzo stary, a informacje nie są zgodne z bieżącym bieżnikiem (2017), Poniżej znajduje się uzupełnienie:

  1. Opal jest kompilatorem typu Ruby to JavaScript typu źródło-źródło. Ma także implementację Ruby Corelib, jest to obecnie bardzo aktywny program rozwojowy i istnieje wiele prac nad nim (frontend). i gotowe do produkcji. Ponieważ oparty na javascript, nie obsługuje wątków równoległych.

  2. truffleruby to wysokowydajna implementacja języka programowania Ruby. Zbudowany na GraalVM przez Oracle Labs, TruffleRuby jest rozwidleniem JRuby, łączącym go z kodem z projektu Rubinius, a także zawierającym kod ze standardowej implementacji Ruby, MRI, wciąż na żywo, nie gotowy do produkcji. Ta wersja Ruby wydaje się być stworzona dla wydajności, nie wiem, czy obsługuje równoległe wątki, ale myślę, że powinna.

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.