루비 멀티스레딩


시스템에서 실행되는 모든 프로그램은 프로세스입니다. 각 프로세스에는 하나 이상의 스레드가 포함됩니다.

스레드는 프로그램의 단일 순차적 제어 프로세스입니다. 단일 프로그램에서 여러 스레드를 동시에 실행하여 다양한 작업을 완료하는 것을 멀티스레딩이라고 합니다.

Ruby에서는 Thread 클래스를 통해 다중 스레드를 생성할 수 있습니다. Ruby의 스레드는 가볍고 효율적인 방식으로 병렬 코드를 구현할 수 있습니다.


Ruby 스레드 만들기

새 스레드를 시작하려면 Thread.new를 호출하면 됩니다.

# 线程 #1 代码部分
Thread.new {
  # 线程 #2 执行代码
}
# 线程 #1 执行代码

Example

다음 예는 Ruby 프로그램에서 멀티스레딩을 사용하는 방법을 보여줍니다.

#!/usr/bin/ruby

def func1
   i=0
   while i<=2
      puts "func1 at: #{Time.now}"
      sleep(2)
      i=i+1
   end
end

def func2
   j=0
   while j<=2
      puts "func2 at: #{Time.now}"
      sleep(1)
      j=j+1
   end
end

puts "Started At #{Time.now}"
t1=Thread.new{func1()}
t2=Thread.new{func2()}
t1.join
t2.join
puts "End at #{Time.now}"


위 코드 실행 결과는 다음과 같습니다.

Started At Wed May 14 08:21:54 -0700 2014
func1 at: Wed May 14 08:21:54 -0700 2014
func2 at: Wed May 14 08:21:54 -0700 2014
func2 at: Wed May 14 08:21:55 -0700 2014
func1 at: Wed May 14 08:21:56 -0700 2014
func2 at: Wed May 14 08:21:56 -0700 2014
func1 at: Wed May 14 08:21:58 -0700 2014
End at Wed May 14 08:22:00 -0700 2014

스레드 수명 주기

1. Thread.new를 사용하여 스레드를 만들 수 있습니다. 또한 동일한 구문으로 Thread.start 또는 Thread.fork 세 가지 메서드를 사용하여 스레드를 만들 수도 있습니다.

2. 스레드를 생성한 후 시작할 필요가 없으며 스레드가 자동으로 실행됩니다.

3. Thread 클래스는 스레드를 제어하는 ​​몇 가지 메서드를 정의합니다. 스레드는 Thread.new의 코드 블록을 실행합니다.

4. 스레드 코드 블록의 마지막 문은 스레드 메서드를 통해 호출할 수 있는 스레드 값입니다. 스레드 실행이 완료되면 스레드 값이 반환됩니다. 스레드 실행이 완료되었습니다.

5. Thread.current 메서드는 현재 스레드를 나타내는 개체를 반환합니다. Thread.main 메소드는 메인 스레드로 돌아갑니다.

6. Thread.Join 메서드를 통해 스레드를 실행합니다. 이 메서드는 현재 스레드가 실행을 완료할 때까지 기본 스레드를 일시 중지합니다.


스레드 상태

스레드에는 5가지 상태가 있습니다.

스레드 상태반환 값
Executablerun
SleepingSlee ping
Exitaborting
정상 종료false
비정상 종료 발생nil

스레드 및 예외

스레드에서 예외가 발생하고 구조에 의해 포착되지 않으면 일반적으로 스레드는 경고 없이 종료됩니다. 그러나 Thread#join으로 인해 다른 스레드가 이 스레드를 기다리고 있는 경우 대기 중인 스레드에도 동일한 예외가 발생합니다.

begin
  t = Thread.new do
    Thread.pass    # 主线程确实在等join
    raise "unhandled exception"
  end
  t.join
rescue
  p $!  # => "unhandled exception"
end

예외로 인해 스레드가 종료될 때 인터프리터가 중단되도록 하려면 다음 3가지 방법을 사용하세요.

  • 스크립트 시작 시 -d 옵션을 지정하고 디버그 모드로 실행하세요.

  • Thread.abort_on_Exception으로 플래그를 설정하세요. Thread.abort_on_exception设置标志。

  • 使用Thread#abort_on_exception

지정된 스레드에 플래그를 설정하려면 Thread#abort_on_Exception을 사용하세요.


위 3가지 방법 중 하나를 사용하면 통역사 전체가 중단됩니다.

t = Thread.new { ... }
t.abort_on_exception = true

스레드 동기화 제어

Ruby에서는 동기화를 달성하는 세 가지 방법이 제공됩니다.

1. 스레드 동기화는 Mutex 클래스를 통해 이루어집니다.

2 데이터 핸드오버를 감독하는 Queue 클래스는 스레드 동기화를 구현합니다. 3. ConditionVariable을 사용하여 동기화 제어 구현

쓰레드 동기화는 Mutex 클래스를 통해 구현됩니다

Thread 동기화 제어는 Mutex 클래스를 통해 동시에 구현됩니다. 프로그램 변수가 동시에 여러 스레드에 필요한 경우 이 변수를 부분적으로 잠글 수 있습니다. 자물쇠를 사용하여. 코드는 다음과 같습니다.

#!/usr/bin/ruby

require "thread"
puts "Synchronize Thread"

@num=200
@mutex=Mutex.new

def buyTicket(num)
     @mutex.lock
          if @num>=num
               @num=@num-num
               puts "you have successfully bought #{num} tickets"
          else
               puts "sorry,no enough tickets"
          end
     @mutex.unlock
end

ticket1=Thread.new 10 do
     10.times do |value|
     ticketNum=15
     buyTicket(ticketNum)
     sleep 0.01
     end
end

ticket2=Thread.new 10 do
     10.times do |value|
     ticketNum=20
     buyTicket(ticketNum)
     sleep 0.01
     end
end

sleep 1
ticket1.join
ticket2.join

위 코드의 실행 결과는 다음과 같습니다.

Synchronize Thread
you have successfully bought 15 tickets
you have successfully bought 20 tickets
you have successfully bought 15 tickets
you have successfully bought 20 tickets
you have successfully bought 15 tickets
you have successfully bought 20 tickets
you have successfully bought 15 tickets
you have successfully bought 20 tickets
you have successfully bought 15 tickets
you have successfully bought 20 tickets
you have successfully bought 15 tickets
sorry,no enough tickets
sorry,no enough tickets
sorry,no enough tickets
sorry,no enough tickets
sorry,no enough tickets
sorry,no enough tickets
sorry,no enough tickets
sorry,no enough tickets
sorry,no enough tickets

lock을 사용하여 변수를 잠그는 것 외에도 try_lock을 사용하여 변수를 잠글 수도 있고 Mutex.synchronize를 사용할 수도 있습니다. 특정 변수에 대한 액세스를 동기화합니다.

데이터 전송을 감독하는 Queue 클래스는 스레드 동기화를 구현합니다.

Queue 클래스는 스레드를 지원하고 대기열의 끝에 동기적으로 액세스할 수 있는 대기열을 나타냅니다. 서로 다른 스레드가 동일한 쌍 클래스를 사용할 수 있지만 이 대기열의 데이터가 동기화될 수 있는지 걱정할 필요가 없습니다. 또한 SizedQueue 클래스를 사용하면 대기열 길이를 제한할 수 있어 매우 편리합니다. 스레드 동기화 애플리케이션을 개발해야 합니다. 이 대기열에 참여하는 한 스레드 동기화에 대해 걱정할 필요가 없습니다.

고전적인 생산자-소비자 문제:

#!/usr/bin/ruby

require "thread"
puts "SizedQuee Test"

queue = Queue.new

producer = Thread.new do
     10.times do |i|
          sleep rand(i) # 让线程睡眠一段时间
          queue << i
          puts "#{i} produced"
     end
end

consumer = Thread.new do
     10.times do |i|
          value = queue.pop
          sleep rand(i/2)
          puts "consumed #{value}"
     end
end

consumer.join


프로그램 출력:

SizedQuee Test
0 produced
1 produced
consumed 0
2 produced
consumed 1
consumed 2
3 produced
consumed 34 produced

consumed 4
5 produced
consumed 5
6 produced
consumed 6
7 produced
consumed 7
8 produced
9 produced
consumed 8
consumed 9


스레드 변수

스레드는 자체 개인 변수를 가질 수 있으며 스레드의 개인 변수는 스레드가 생성될 때 스레드에 기록됩니다. 스레드 범위 내에서 사용할 수 있지만 스레드 외부에서 공유할 수는 없습니다.

하지만 가끔 스레드의 로컬 변수에 다른 스레드나 메인 스레드가 액세스해야 하는 경우 어떻게 해야 할까요? Ruby는 스레드를 해시 테이블로 처리하는 것과 유사하게 이름으로 스레드 변수를 생성하는 방법을 제공합니다. []=를 통해 데이터를 쓰고 []를 통해 데이터를 읽습니다. 다음 코드를 살펴보겠습니다.

#!/usr/bin/ruby

count = 0
arr = []

10.times do |i|
   arr[i] = Thread.new {
      sleep(rand(0)/10.0)
      Thread.current["mycount"] = count
      count += 1
   }
end

arr.each {|t| t.join; print t["mycount"], ", " }
puts "count = #{count}"

위 코드의 출력은 다음과 같습니다.

8, 0, 3, 7, 2, 1, 6, 5, 4, 9, count = 10

메인 스레드는 하위 스레드의 실행이 완료될 때까지 기다린 후 각 값을 별도로 출력합니다. .


스레드 우선순위

스레드의 우선순위는 스레드 스케줄링에 영향을 미치는 주요 요소입니다. 다른 요인으로는 CPU가 차지하는 실행 시간, 스레드 그룹화 스케줄링 등이 있습니다.

Thread.priority 메서드를 사용하여 스레드 우선순위를 가져오고 Thread.priority= 메서드를 사용하여 스레드 우선순위를 조정할 수 있습니다.

스레드 우선순위는 기본적으로 0입니다. 우선순위가 높은 것들은 더 빠르게 실행됩니다.

스레드는 자체 범위에 있는 모든 데이터에 액세스할 수 있지만, 스레드에 있는 다른 스레드의 데이터에 액세스해야 하는 경우 어떻게 해야 합니까? Thread 클래스는 스레드 데이터가 서로 액세스할 수 있는 방법을 제공합니다. 간단히 스레드를 해시 테이블로 사용하여 데이터를 쓰고 []를 사용하여 모든 스레드에서 데이터를 읽을 수 있습니다.

athr = Thread.new { Thread.current["name"] = "Thread A"; Thread.stop }
bthr = Thread.new { Thread.current["name"] = "Thread B"; Thread.stop }
cthr = Thread.new { Thread.current["name"] = "Thread C"; Thread.stop }
Thread.list.each {|x| puts "#{x.inspect}: #{x["name"]}" }

스레드를 해시 테이블로 처리하고 [] 및 []= 메서드를 사용하면 스레드 간 데이터 공유가 실현되는 것을 볼 수 있습니다.


Thread Mutual Exclusion

Mutex(Mutal Exclusion = Mutual Exclusion Lock)는 두 스레드가 동일한 공용 리소스(예: 전역 변수)를 동시에 읽고 쓰는 것을 방지하기 위해 다중 스레드 프로그래밍에 사용되는 메커니즘입니다.

Mutax가 없는 인스턴스

#!/usr/bin/ruby
require 'thread'

count1 = count2 = 0
difference = 0
counter = Thread.new do
   loop do
      count1 += 1
      count2 += 1
   end
end
spy = Thread.new do
   loop do
      difference += (count1 - count2).abs
   end
end
sleep 1
puts "count1 :  #{count1}"
puts "count2 :  #{count2}"
puts "difference : #{difference}"

위 예제의 출력 결과는 다음과 같습니다.

count1 :  9712487
count2 :  12501239
difference : 0

Mutax를 사용하는 인스턴스

#!/usr/bin/ruby
require 'thread'
mutex = Mutex.new

count1 = count2 = 0
difference = 0
counter = Thread.new do
   loop do
      mutex.synchronize do
         count1 += 1
         count2 += 1
      end
    end
end
spy = Thread.new do
   loop do
       mutex.synchronize do
          difference += (count1 - count2).abs
       end
   end
end
sleep 1
mutex.lock
puts "count1 :  #{count1}"
puts "count2 :  #{count2}"
puts "difference : #{difference}"

위 예제의 출력 결과는 다음과 같습니다.

count1 :  1336406
count2 :  1336406
difference : 0

Deadlock

두 개 이상의 컴퓨팅 장치, 양쪽 양 당사자가 상대방이 시스템 자원을 얻기 위해 실행을 중지할 때까지 기다리고 있지만 어느 당사자도 일찍 종료하지 않는 경우 이러한 상황을 교착 상태라고 합니다.

예를 들어 프로세스 p1이 모니터를 점유하면서 동시에 프린터를 사용해야 하고, 프로세스 p2가 프린터를 점유하고 p2가 모니터를 사용해야 하므로 교착상태가 발생합니다.

Mutex 객체를 사용할 때 스레드 교착 상태에 주의해야 합니다.

Example

#!/usr/bin/ruby
require 'thread'
mutex = Mutex.new

cv = ConditionVariable.new
a = Thread.new {
   mutex.synchronize {
      puts "A: I have critical section, but will wait for cv"
      cv.wait(mutex)
      puts "A: I have critical section again! I rule!"
   }
}

puts "(Later, back at the ranch...)"

b = Thread.new {
   mutex.synchronize {
      puts "B: Now I am critical, but am done with cv"
      cv.signal
      puts "B: I am still critical, finishing up"
   }
}
a.join
b.join

위 예제의 출력 결과는 다음과 같습니다.

A: I have critical section, but will wait for cv
(Later, back at the ranch...)
B: Now I am critical, but am done with cv
B: I am still critical, finishing up
A: I have critical section again! I rule!

Thread 클래스 메서드

전체 Thread 클래스 메서드는 다음과 같습니다.

일련 번호메서드 설명
1Thread.abort_on_Exception
값이 true인 경우 예외로 인해 스레드가 종료되면 전체 인터프리터가 중단됩니다. 기본값은 false입니다. 즉, 일반적인 상황에서 스레드에서 예외가 발생하고 Thread#join 등으로 예외가 감지되지 않으면 스레드가 경고 없이 종료됩니다.
2Thread.abort_on_Exception=
true로 설정하면 예외로 인해 스레드가 종료되면 전체 인터프리터가 중단됩니다. 새로운 상태를 반환합니다.
3Thread.tical
부울 값을 반환합니다.
4Thread.tical=
값이 true이면 스레드 전환이 발생하지 않습니다. 현재 스레드가 정지(중지)되거나 신호가 개입하면 해당 값은 자동으로 false로 변경됩니다.
5Thread.current
현재 실행 중인 스레드(현재 스레드)를 반환합니다.
6Thread.exit
현재 스레드의 실행을 종료합니다. 현재 스레드를 반환합니다. 현재 스레드가 유일한 스레드인 경우 해당 작업을 종료하는 데exit(0)이 사용됩니다.
7Thread.fork { block }
스레드를 생성하는 Thread.new와 동일합니다.
8Thread.kill( aThread )
스레드 실행을 종료합니다.
9Thread.list
실행 중이거나 일시 중지된 상태의 라이브 스레드 배열을 반환합니다.
10Thread.main
메인 스레드로 돌아갑니다.
11Thread.new( [ arg ]* ) {| args | block }
스레드를 생성하고 실행을 시작합니다. 숫자는 변경되지 않고 블록에 전달됩니다. 이를 통해 스레드를 시작할 때 값이 스레드에 고유한 지역 변수에 전달될 수 있습니다.
12Thread.pass
는 실행 중인 스레드의 상태를 변경하지 않지만 다른 실행 가능한 스레드에 제어권을 부여합니다(명시적 스레드 예약).
13Thread.start( [ args ]* ) {| args | block }
스레드를 생성하고 실행을 시작합니다. 숫자는 변경되지 않고 블록에 전달됩니다. 이를 통해 스레드를 시작할 때 값이 스레드에 고유한 지역 변수에 전달될 수 있습니다.
14Thread.stop
다른 스레드가 run 메서드를 사용하여 스레드를 다시 깨울 때까지 현재 스레드를 일시 중단합니다.

스레드 인스턴스화 방법

다음 예에서는 스레드 인스턴스화 방법인 Join을 호출합니다.

#!/usr/bin/ruby

thr = Thread.new do   # 实例化
   puts "In second thread"
   raise "Raise exception"
end
thr.join   # 调用实例化方法 join

다음은 인스턴스화 방법의 전체 목록입니다.

일련번호방법 설명
1thr[ name ]
스레드의 이름에 해당하는 고유 데이터를 검색합니다. 이름은 문자열이나 기호일 수 있습니다. name에 해당하는 데이터가 없으면 nil을 반환한다.
2thr[ name ] =
스레드의 이름에 해당하는 고유 데이터 값을 설정합니다. 이름은 문자열 또는 기호일 수 있습니다. nil로 설정하면 스레드의 해당 데이터가 삭제됩니다.
3thr.abort_on_Exception
부울 값을 반환합니다.
4thr.abort_on_Exception=
값이 true인 경우 예외로 인해 스레드가 종료되면 전체 인터프리터가 중단됩니다.
5thr.alive?
스레드가 "살아있다"면 true를 반환합니다.
6thr.exit
스레드 실행을 종료합니다. 스스로 돌아오세요.
7thr.join
자체 스레드가 종료될 때까지 현재 스레드를 일시 중단합니다. 예외로 인해 자체 종료되면 현재 스레드에서 동일한 예외가 발생합니다.
8thr.key?
name에 해당하는 스레드별 데이터가 정의되어 있으면 true를 반환합니다
9thr.kill
Thread.exit 과 유사합니다.
10thr.priority
스레드의 우선순위를 반환합니다. 우선순위의 기본값은 0입니다. 값이 클수록 우선순위가 높아집니다.
11thr.priority=
스레드의 우선순위를 음수로 설정할 수도 있습니다.
12thr.raise( anException )
이 스레드에서 강제로 예외를 발생시킵니다.
13 thr .run
일시 중지된(중지) 스레드를 다시 시작합니다. wakeup과 달리 스레드를 즉시 전환합니다. 죽은 프로세스에서 이 메서드를 사용하면 ThreadError 예외가 발생합니다.
14 thr. safe_level
현재 스레드의 safe_level은 $SAFE와 동일합니다.
15thr.status
문자열 "run", "sleep" 또는 "aborting"을 사용하세요. 스레드가 정상적으로 종료되면 false를 반환하고, 예외로 인해 종료되면 nil을 반환합니다.
16thr.stop?
스레드가 종료된 상태(죽음) 또는 일시 중지(중지)인 경우 true를 반환합니다.
17thr.value
스레드가 종료된 후(조인과 동일) 스레드 실행 중에 예외가 발생하면 해당 예외가 다시 발생합니다.
18thr.wakeup
일시 중단된(중지) 스레드의 상태를 실행 가능 상태(실행)로 변경합니다. 이 메서드가 죽은 스레드에서 실행되면 ThreadError 예외가 발생합니다.