Apakah ruby ​​memiliki multithreading nyata?

295

Saya tahu tentang "koperasi" threading ruby ​​menggunakan benang hijau . Bagaimana saya bisa membuat utas "tingkat-OS" yang nyata dalam aplikasi saya untuk memanfaatkan beberapa inti cpu untuk diproses?

skolima
sumber

Jawaban:

612

Diperbarui dengan komentar Jörg's September 2011

Anda tampaknya membingungkan dua hal yang sangat berbeda di sini: Bahasa Pemrograman Ruby dan model threading spesifik dari satu implementasi spesifik Bahasa Pemrograman Ruby. Saat ini ada sekitar 11 implementasi yang berbeda dari Bahasa Pemrograman Ruby, dengan model threading yang sangat berbeda dan unik.

(Sayangnya, hanya dua dari 11 implementasi yang benar-benar siap untuk penggunaan produksi, tetapi pada akhir tahun jumlah itu mungkin akan naik hingga empat atau lima.) ( Pembaruan : sekarang 5: MRI, JRuby, YARV (penerjemah) untuk Ruby 1.9), Rubinius dan IronRuby).

  1. Implementasi pertama sebenarnya tidak memiliki nama, yang membuatnya cukup canggung untuk merujuknya dan benar-benar menjengkelkan dan membingungkan. Paling sering disebut sebagai "Ruby", yang bahkan lebih menjengkelkan dan membingungkan daripada tidak memiliki nama, karena itu mengarah pada kebingungan tanpa akhir antara fitur Bahasa Pemrograman Ruby dan Implementasi Ruby tertentu.

    Itu juga kadang-kadang disebut "MRI" (untuk "Implementasi Ruby Matz"), CRuby atau MatzRuby.

    MRI mengimplementasikan Ruby Threads sebagai Green Threads dalam interpreternya . Sayangnya, itu tidak memungkinkan utas tersebut dijadwalkan secara paralel, mereka hanya dapat menjalankan satu utas pada satu waktu.

    Namun, sejumlah C Threads (POSIX Threads dll.) Dapat berjalan secara paralel dengan Thread Ruby, sehingga Perpustakaan C eksternal, atau Ekstensi MRI C yang membuat thread mereka sendiri masih dapat berjalan secara paralel.

  2. Implementasi kedua adalah YARV (kependekan dari "Yet Another Ruby VM"). YARV mengimplementasikan Thread Ruby sebagai POSIX atau Windows NT Threads , namun, ia menggunakan Global Interpreter Lock (GIL) untuk memastikan bahwa hanya satu Thread Ruby yang benar-benar dapat dijadwalkan pada satu waktu.

    Seperti MRI, C Threads sebenarnya dapat berjalan paralel ke Ruby Threads.

    Di masa depan, adalah mungkin, bahwa GIL dapat dipecah menjadi kunci yang lebih halus, sehingga memungkinkan semakin banyak kode untuk berjalan secara paralel, tetapi itu sangat jauh, bahkan belum direncanakan .

  3. JRuby mengimplementasikan Ruby Threads sebagai Native Threads , di mana "Native Threads" dalam kasus JVM jelas berarti "JVM Threads". JRuby tidak memberlakukan penguncian tambahan pada mereka. Jadi, apakah utas tersebut benar-benar dapat berjalan secara paralel tergantung pada JVM: beberapa JVM menerapkan JVM Threads sebagai OS Threads dan beberapa sebagai Green Threads. (JVM mainstream dari Sun / Oracle menggunakan utas OS secara eksklusif sejak JDK 1.3)

  4. XRuby juga mengimplementasikan Ruby Threads sebagai JVM Threads . Pembaruan : XRuby sudah mati.

  5. IronRuby mengimplementasikan Ruby Threads sebagai Native Threads , di mana "Native Threads" dalam kasus CLR jelas berarti "CLR Threads". IronRuby tidak memaksakan penguncian tambahan pada mereka, jadi, mereka harus berjalan secara paralel, selama CLR Anda mendukungnya.

  6. Ruby.NET juga mengimplementasikan Thread Ruby sebagai Thread CLR . Pembaruan: Ruby.NET sudah mati.

  7. Rubinius mengimplementasikan Ruby Threads sebagai Green Threads dalam Mesin Virtualnya . Lebih tepatnya: Rubinius VM mengekspor konkurensi / paralelisme / kontrol aliran non-lokal yang sangat ringan, sangat fleksibel, yang disebut " Tugas ", dan semua konstruksi konkurensi lainnya (Utas dalam diskusi ini, tetapi juga Lanjutan , Aktor , dan hal-hal lain ) diimplementasikan dalam Ruby murni, menggunakan Tugas.

    Rubinius tidak dapat (saat ini) menjadwalkan Thread secara paralel, namun menambahkan bahwa tidak terlalu masalah: Rubinius sudah dapat menjalankan beberapa instance VM di beberapa Thread POSIX secara paralel , dalam satu proses Rubinius. Karena Thread sebenarnya diimplementasikan dalam Ruby, mereka dapat, seperti objek Ruby lainnya, diserialisasi dan dikirim ke VM yang berbeda di Thread POSIX yang berbeda. (Itu model yang sama yang digunakan BEAM Erlang VM untuk konkurensi SMP. Ini sudah diterapkan untuk Rubinius Actors .)

    Pembaruan : Informasi tentang Rubinius dalam jawaban ini adalah tentang Shotgun VM, yang tidak ada lagi. VM C + + "baru" tidak menggunakan utas hijau yang dijadwalkan di beberapa VM (yaitu gaya Erlang / BEAM), ini menggunakan VM tunggal yang lebih tradisional dengan beberapa model utas OS asli, seperti yang digunakan oleh, katakanlah, CLR, Mono , dan hampir setiap JVM.

  8. MacRuby dimulai sebagai port YARV di atas Objective-C Runtime dan CoreFoundation dan Cocoa Frameworks. Sekarang telah menyimpang secara signifikan dari YARV, tetapi AFAIK saat ini masih berbagi Model Threading yang sama dengan YARV . Pembaruan: MacRuby tergantung pada pengumpul sampah apel yang dinyatakan usang dan akan dihapus di versi MacOSX selanjutnya, MacRuby tidak hidup.

  9. Kardinal adalah Implementasi Ruby untuk Mesin Virtual Parrot . Itu belum mengimplementasikan utas, namun, jika sudah, mungkin akan menerapkannya sebagai Parrot Threads . Pembaruan : Cardinal tampaknya sangat tidak aktif / mati.

  10. MagLev adalah Implementasi Ruby untuk GemStone / S Smalltalk VM . Saya tidak punya informasi apa model threading yang digunakan GemStone / S, apa model threading yang MagLev gunakan atau bahkan jika utas bahkan diimplementasikan (mungkin tidak).

  11. HotRuby adalah tidak Pelaksana Ruby penuh sendiri. Ini adalah implementasi dari bytecode VM YARV dalam JavaScript. HotRuby tidak mendukung utas (belum?) Dan ketika itu terjadi, mereka tidak akan dapat berjalan secara paralel, karena JavaScript tidak memiliki dukungan untuk paralelisme sejati. Ada versi ActionScript dari HotRuby, dan ActionScript mungkin sebenarnya mendukung paralelisme. Pembaruan : HotRuby sudah mati.

Sayangnya, hanya dua dari 11 Implementasi Ruby ini yang benar-benar siap produksi: MRI dan JRuby.

Jadi, jika Anda menginginkan thread paralel sejati, JRuby saat ini adalah satu-satunya pilihan Anda - bukan itu yang buruk: JRuby sebenarnya lebih cepat daripada MRI, dan bisa dibilang lebih stabil.

Jika tidak, solusi Ruby "klasik" adalah menggunakan proses alih-alih utas untuk paralelisme. Perpustakaan Ruby Core berisi Processmodul dengan Process.fork metode yang membuatnya mudah untuk memotong proses Ruby lainnya. Selain itu, Pustaka Standar Ruby berisi pustaka Ruby Terdistribusi (dRuby / dRb) , yang memungkinkan kode Ruby didistribusikan secara sepele melalui beberapa proses, tidak hanya pada mesin yang sama tetapi juga di seluruh jaringan.

Jörg W Mittag
sumber
1
tetapi menggunakan garpu akan memutuskan penggunaan pada jruby ... hanya mengatakan
akostadinov
1
Ini jawaban yang bagus. Namun itu tergantung pada banyak tautan busuk. Saya tidak tahu ke mana sumber daya ini mungkin pindah.
BlackVegetable
28

Ruby 1.8 hanya memiliki utas hijau, tidak ada cara untuk membuat utas "OS-level" nyata. Tetapi, ruby ​​1.9 akan memiliki fitur baru yang disebut serat, yang akan memungkinkan Anda untuk membuat utas tingkat-OS yang sebenarnya. Sayangnya, Ruby 1.9 masih dalam versi beta, dijadwalkan stabil dalam beberapa bulan.

Alternatif lain adalah menggunakan JRuby. JRuby mengimplementasikan utas sebagai thead tingkat OS, tidak ada "utas hijau" di dalamnya. Versi terbaru JRuby adalah 1.1.4 dan setara dengan Ruby 1.8

Josh Moore
sumber
35
Adalah salah bahwa Ruby 1.8 hanya memiliki utas hijau, beberapa implementasi dari Ruby 1.8 memiliki utas asli: JRuby, XRuby, Ruby.NET dan IronRuby. Serat tidak memungkinkan pembuatan utas asli, mereka lebih ringan daripada utas. Mereka sebenarnya semi-coroutine, yaitu mereka kooperatif.
Jörg W Mittag
19
Saya pikir itu cukup jelas dari jawaban Josh bahwa maksudnya Ruby 1.8 adalah runtime, alias MRI, dan bukan Ruby 1.8 bahasa, ketika dia mengatakan Ruby 1.8.
Theo
@Theo Juga jelas bahwa dia mengacaukan konsep dalam jawabannya. Serat bukan cara untuk membuat utas asli, seperti yang telah disebutkan, mereka bahkan merupakan benda yang lebih ringan daripada utas dan saat ini yang kasar memiliki utas asli tetapi dengan GIL.
Foo Bar Zoo
8

Itu tergantung pada implementasinya:

  • MRI tidak punya, YARV lebih dekat.
  • JRuby dan MacRuby miliki.




Ruby memiliki penutupan sebagai Blocks, lambdasdan Procs. Untuk memanfaatkan sepenuhnya penutupan dan banyak inti di JRuby, eksekutor Java berguna; untuk MacRuby saya suka antrian GCD .

Perhatikan bahwa, mampu membuat utas "tingkat-OS" yang sebenarnya tidak menyiratkan bahwa Anda dapat menggunakan beberapa inti cpu untuk pemrosesan paralel. Lihatlah contoh di bawah ini.

Ini adalah output dari program Ruby sederhana yang menggunakan 3 utas menggunakan 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 

Seperti yang Anda lihat di sini, ada empat utas OS, namun hanya yang dengan status Rsedang berjalan. Ini karena keterbatasan dalam bagaimana benang-benang Ruby diimplementasikan.



Program yang sama, sekarang dengan JRuby. Anda dapat melihat tiga utas dengan status R, yang berarti mereka berjalan secara paralel.

(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 


Program yang sama, sekarang dengan MacRuby. Ada juga tiga utas yang berjalan secara paralel. Ini karena utas MacRuby adalah utas POSIX ( utas "OS-level" asli ) dan tidak ada 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 


Sekali lagi, program yang sama tetapi sekarang dengan MRI tua yang baik. Karena kenyataan bahwa implementasi ini menggunakan green-threads, hanya satu utas yang muncul

(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



Jika Anda tertarik dengan Ruby multi-threading, Anda mungkin menemukan laporan saya Debugging program paralel menggunakan fork handler menarik.
Untuk gambaran umum yang lebih umum dari Ruby internal Ruby Under a Microscope adalah bacaan yang baik.
Juga, Ruby Threads dan Global Interpreter Lock di C di Omniref menjelaskan dalam kode sumber mengapa thread Ruby tidak berjalan secara paralel.

pengguna454322
sumber
Dengan RMI, maksud Anda MRI?
Mayuresh Srivastava
4

Bagaimana kalau menggunakan drb ? Ini bukan multi-threading yang sebenarnya tetapi komunikasi antara beberapa proses, tetapi Anda dapat menggunakannya sekarang di 1.8 dan gesekan yang cukup rendah.

ujh
sumber
3

Saya akan membiarkan "Monitor Sistem" menjawab pertanyaan ini. Saya mengeksekusi kode yang sama (di bawah, yang menghitung bilangan prima) dengan 8 thread Ruby yang berjalan pada mesin i7 (4 hyperthreaded-core) dalam kedua kasus ... jalankan pertama adalah dengan:

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

Yang kedua adalah dengan:

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

Menariknya, CPU lebih tinggi untuk utas JRuby, tetapi waktu penyelesaiannya sedikit lebih pendek untuk Ruby yang ditafsirkan. Agak sulit untuk mengetahui dari grafik, tetapi menjalankan kedua (ditafsirkan Ruby) menggunakan sekitar 1/2 CPU (tidak ada hyperthreading?)

masukkan deskripsi gambar di sini

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
Kue Groovy
sumber
1

Jika Anda menggunakan MRI, maka Anda dapat menulis kode berulir dalam C baik sebagai ekstensi atau menggunakan permata ruby-inline.


sumber
1

Jika Anda benar-benar membutuhkan paralelisme di Ruby untuk sistem tingkat Produksi (di mana Anda tidak dapat menggunakan beta) proses mungkin merupakan alternatif yang lebih baik.
Tapi, yang paling pasti patut dicoba terlebih dahulu di bawah JRuby.

Juga jika Anda tertarik untuk melakukan threading selanjutnya di bawah Ruby, Anda mungkin menemukan artikel ini bermanfaat.

Pascal
sumber
JRuby adalah pilihan yang bagus. Untuk pemrosesan paralel menggunakan proses, saya suka github.com/grosser/parallel Parallel.map(['a','b','c'], :in_processes=>3){...
user454322
1

Karena tidak dapat mengedit jawaban itu, maka tambahkan balasan baru di sini.

Pembaruan (2017-05-08)

Artikel ini sudah sangat lama, dan informasi tidak mengikuti tapak saat ini (2017), Berikut adalah beberapa suplemen:

  1. Opal adalah kompiler sumber-ke-sumber Ruby ke JavaScript. Ini juga memiliki implementasi Ruby corelib, Ini saat ini sangat aktif develompent, dan ada banyak kerangka kerja (frontend) bekerja di dalamnya. dan produksi siap. Karena berdasarkan javascript, itu tidak mendukung utas paralel.

  2. truffleruby adalah implementasi kinerja tinggi dari bahasa pemrograman Ruby. Dibangun di atas GraalVM oleh Oracle Labs, TruffleRuby adalah cabang dari JRuby, menggabungkannya dengan kode dari proyek Rubinius, dan juga berisi kode dari implementasi standar Ruby, MRI, masih pengembangan langsung, belum siap produksi. Ruby versi ini sepertinya terlahir untuk kinerja, saya tidak tahu apakah mendukung thread paralel, tapi saya pikir seharusnya begitu.

zw963
sumber