[ruby] 루비에 실제 멀티 스레딩이 있습니까?

나는 녹색 스레드를 사용하는 루비의 “협동”스레딩에 대해 알고 있습니다. 처리에 여러 CPU 코어를 사용하기 위해 애플리케이션에서 실제 “OS 레벨”스레드를 작성하려면 어떻게해야합니까?



답변

Jörg의 2011 년 9 월 코멘트로 업데이트 됨

여기에 Ruby 프로그래밍 언어와 Ruby 프로그래밍 언어의 특정 구현에 대한 특정 스레딩 모델이라는 두 가지 매우 혼란스러운 내용이 있습니다. 현재 매우 다른 고유 스레딩 모델 을 사용하여 약 11 가지의 Ruby 프로그래밍 언어 구현이 있습니다.

(불행하게도, 그 (11 개)의 구현 만이 실제로 생산 사용에 대한 준비가되어 있지만, 올해 말까지 수는 아마 네다섯까지 갈 것입니다.) ( 업데이트 : MRI, JRuby를, YARV (통역 : 지금은 5입니다 Ruby 1.9), Rubinius 및 IronRuby).

  1. 첫 번째 구현에는 실제로 이름이 없으므로 참조하기가 상당히 어려워지고 실제로 성 가시고 혼란 스럽습니다. 루비 프로그래밍 언어의 기능과 특정 루비 구현 사이의 끝없는 혼란을 초래하기 때문에 이름이없는 것보다 훨씬 더 성 가시고 혼란스러운 “루비”라고합니다.

    “MRI”( “Matz ‘s Ruby Implementation”), CRuby 또는 MatzRuby라고도합니다.

    MRI는 인터프리터 내에서 Ruby Threads를 Green Threads로 구현 합니다. 불행히도 스레드를 병렬로 예약 할 수 없으며 한 번에 하나의 스레드 만 실행할 수 있습니다.

    그러나 여러 C 스레드 (POSIX 스레드 등)를 Ruby 스레드와 병렬로 실행할 수 있으므로 자체 스레드를 작성하는 외부 C 라이브러리 또는 MRI C 확장은 여전히 ​​병렬로 실행될 수 있습니다.

  2. 두 번째 구현은 YARV ( “Yet Another Ruby VM”의 약자)입니다. YARV는 Ruby 스레드를 POSIX 또는 Windows NT 스레드로 구현 하지만 GIL (Global Interpreter Lock)을 사용하여 한 번에 하나의 Ruby 스레드 만 실제로 스케줄 할 수 있습니다.

    MRI와 마찬가지로 C 스레드 실제로 Ruby 스레드와 병렬로 실행될 수 있습니다 .

    앞으로 GIL 더 세밀한 잠금으로 분류되어 점점 더 많은 코드가 실제로 병렬로 실행될 수 있지만 아직 멀었으므로 아직 계획 되지 않았습니다.

  3. JRuby 는 Ruby Threads를 Native Threads로 구현합니다 . 여기서 JVM의 경우 “Native Threads”는 분명히 “JVM Threads”를 의미합니다. JRuby는 추가 잠금을 부과하지 않습니다. 따라서 해당 스레드가 실제로 병렬로 실행될 수 있는지 여부는 JVM에 따라 다릅니다. 일부 JVM은 JVM 스레드를 OS 스레드로 구현하고 일부는 녹색 스레드로 구현합니다. (Sun / Oracle의 주류 JVM은 JDK 1.3부터 ​​독점적으로 OS 스레드를 사용합니다)

  4. XRubyRuby 스레드를 JVM 스레드로 구현 합니다. 업데이트 : XRuby가 종료되었습니다.

  5. IronRuby 는 Ruby Threads를 Native Threads로 구현합니다 . 여기서 CLR의 경우 “Native Threads”는 분명히 “CLR Threads”를 의미합니다. IronRuby는 추가 잠금을 부과하지 않으므로 CLR이 지원하는 한 병렬로 실행해야합니다.

  6. Ruby.NET 은 또한 Ruby Threads를 CLR Threads로 구현 합니다. 업데이트 : Ruby.NET이 종료되었습니다.

  7. Rubinius 는 가상 머신 내에서 Ruby Threads를 Green Threads로 구현 합니다. 보다 정확하게 : Rubinius VM은 ” 작업 ” 이라고하는 매우 가볍고 매우 유연한 동시성 / 병렬 / 비 로컬 제어 흐름 구성 및 기타 모든 동시성 구성 (이 논의에서는 스레드뿐만 아니라 연속성 , 액터 및 기타 항목)을 내 보냅니다. )는 Tasks를 사용하여 순수한 Ruby로 구현됩니다.

    Rubinius는 스레드를 병렬로 (현재) 예약 할 수 없지만 너무 큰 문제는 아닙니다. Rubinius는 한 Rubinius 프로세스 내 에서 여러 POSIX 스레드의 여러 VM 인스턴스를 병렬로 실행할 수 있습니다 . 스레드는 실제로 루비로 구현되므로 다른 루비 객체와 마찬가지로 직렬화되어 다른 POSIX 스레드의 다른 VM으로 전송 될 수 있습니다. BEAM Erlang VM이 SMP 동시성에 사용 하는 것과 동일한 모델 입니다. 이미 Rubinius Actors에 구현되어 있습니다.

    업데이트 :이 답변의 Rubinius에 대한 정보는 더 이상 존재하지 않는 Shotgun VM에 관한 것입니다. “새로운”C ++ VM은 여러 VM (예 : Erlang / BEAM 스타일)에서 예약 된 녹색 스레드를 사용하지 않습니다. 예를 들어 CLR, Mono에서 사용하는 것과 같은 여러 고유 OS 스레드 모델이있는보다 전통적인 단일 VM을 사용합니다. 그리고 거의 모든 JVM.

  8. MacRuby 는 Objective-C Runtime과 CoreFoundation 및 Cocoa Framework를 기반으로 YARV 포트로 시작했습니다. 이제 YARV에서 크게 분기되었지만 AFAIK는 현재 여전히 YARV와 동일한 스레딩 모델을 공유합니다 .
    업데이트 : MacRuby는 사과 가비지 수집기에 종속되어 있으며 더 이상 사용되지 않으며 MacOSX의 이후 버전에서 제거되며 MacRuby는 언데드입니다.

  9. 카디널앵무새 가상 머신을 위한 루비 구현입니다 . 그것은하면, 그것은 아마로 구현됩니다, 그러나, 아직 스레드를 구현하지 않습니다 앵무새 스레드 . 업데이트 : 추기경은 매우 비활성 / 죽은 것처럼 보입니다.

  10. MagLevGemStone / S Smalltalk VM을 위한 Ruby 구현입니다 . GemStone / S가 사용하는 스레딩 모델, MagLev가 사용하는 스레딩 모델 또는 스레드가 아직 구현 된 경우 (아마도)에 대한 정보가 없습니다.

  11. HotRuby 는 자체 루비 구현 이 아닙니다 . JavaScript로 YARV 바이트 코드 VM을 구현 한 것입니다. HotRuby는 스레드 (아직)를 지원하지 않으며, 그렇지 않으면 JavaScript가 진정한 병렬 처리를 지원하지 않기 때문에 병렬로 실행할 수 없습니다. ActionScript 버전의 HotRuby가 있지만 ActionScript는 실제로 병렬 처리를 지원할 수 있습니다. 업데이트 : HotRuby가 종료되었습니다.

불행히도이 11 가지 Ruby 구현 중 두 가지만 실제로 프로덕션 준비가되었습니다 : MRI 및 JRuby.

따라서 진정한 병렬 스레드를 원한다면 현재 JRuby가 유일한 선택입니다. JRuby는 실제로 MRI보다 빠르며 더 안정적입니다.

그렇지 않으면 “클래식”루비 솔루션은 병렬 처리를 위해 스레드 대신 프로세스를 사용하는 것입니다. Ruby Core Library에는 다른 Ruby 프로세스를 쉽게 제거 할 수 있는
메소드
가 포함 된 Process모듈 이 포함되어 있습니다 . 또한 Ruby 표준 라이브러리에는
분산 루비 (dRuby / dRb) 라이브러리가 포함되어있어 동일한 코드뿐만 아니라 네트워크에서도 여러 프로세스에 루비 코드를 간단하게 배포 할 수 있습니다.Process.fork


답변

Ruby 1.8에는 녹색 스레드 만 있으며 실제 “OS 레벨”스레드를 작성할 수있는 방법이 없습니다. 그러나 루비 1.9에는 파이버라는 새로운 기능이있어 실제 OS 레벨 스레드를 만들 수 있습니다. 불행히도 Ruby 1.9는 아직 베타 버전이며 몇 달 안에 안정적으로 계획되어 있습니다.

다른 대안은 JRuby를 사용하는 것입니다. JRuby는 스레드를 OS 레벨 thead로 구현하며 “그린 스레드”는 없습니다. JRuby의 최신 버전은 1.1.4이며 Ruby 1.8과 같습니다.


답변

구현에 따라 다릅니다.

  • MRI에는 없습니다. YARV가 더 가깝습니다.
  • JRuby와 MacRuby가 있습니다.

루비는 , 및 로 닫힙니다 . JRuby에서 클로저 및 다중 코어를 최대한 활용하기 위해 Java 실행 프로그램 이 유용합니다. MacRuby의 경우 GCD 대기열을 좋아 합니다.
실제 “OS 수준”스레드
를 만들 수 있다는 점에 유의하십시오.BlockslambdasProcs

병렬 처리에 여러 개의 CPU 코어를 사용할 수있는 것은 아닙니다. 아래 예를보십시오.

다음은 Ruby 2.1.0을 사용하여 3 개의 스레드사용하는 간단한 Ruby 프로그램 의 출력입니다 .

(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 

여기에서 볼 수 있듯이 4 개의 OS 스레드가 있지만 상태 R가 있는 스레드 만 실행됩니다. 이것은 Ruby의 스레드가 구현되는 방식에 한계가 있기 때문입니다.


JRuby와 동일한 프로그램. 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 스레드입니다 ( 진짜 “OS 수준”스레드 )와이없는 더 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 

다시 한번, 같은 프로그램이지만 지금은 좋은 MRI를 가지고 있습니다. 이 구현은 그린 스레드를 사용하기 때문에 하나의 스레드 만 나타납니다.

(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 를 잘 읽어보십시오.
또한 Omniref의 C 에서 Ruby Threads와 Global Interpreter Lock은 Ruby 스레드가 병렬로 실행되지 않는 이유를 소스 코드에 설명합니다.


답변

drb 를 사용하는 것은 어떻 습니까? 실제 멀티 스레딩이 아니라 여러 프로세스 간의 통신이지만 1.8에서 사용할 수 있으며 마찰이 적습니다.


답변

“시스템 모니터”가이 질문에 대답하도록하겠습니다. 두 경우 모두 i7 (4 하이퍼 스레드 코어) 컴퓨터에서 8 개의 Ruby 스레드가 실행되는 동일한 코드 (아래의 소수를 계산)를 실행하고 있습니다. 첫 번째 실행은 다음과 같습니다.

jruby 1.5.6 (ruby 1.8.7 patchlevel 249) (2014-02-03 6586) (OpenJDK 64 비트 서버 VM 1.7.0_75) [amd64-java]

두 번째는 다음과 같습니다.

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

흥미롭게도 JRuby 스레드의 CPU는 높지만 해석되는 Ruby의 완료 시간은 약간 짧습니다. 그래프에서 말하기는 어렵지만 두 번째 (해석 된 Ruby) 실행은 CPU의 약 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


답변

MRI를 사용하는 경우 C에서 스레드 코드를 확장 또는 루비 인라인 gem을 사용하여 작성할 수 있습니다.


답변

프로덕션 레벨 시스템 (베타를 사용할 수없는)에 대해 Ruby에서 병렬 처리가 실제로 필요한 경우 프로세스가 더 나은 대안 일 수 있습니다.
그러나 먼저 JRuby에서 스레드를 시도해 볼 가치가 있습니다.

또한 루비에서 쓰레딩에 관심이 있다면이 기사가 유용 할 것입니다.