English | 简体中文 | 繁體中文 | Русский язык | Français | Español | Português | Deutsch | 日本語 | 한국어 | Italiano | بالعربية

Ruby 멀티 스레드

시스템에서 실행 중인 모든 프로그램은 하나의 프로세스입니다. 각 프로세스는 하나나 여러 스레드를 포함합니다.

스레드는 프로그램에서 하나의 순차적인顺序 제어 프로세스로, 여러 스레드가 동시에 여러 작업을 수행하여 다중 스레드라고 합니다.

Ruby에서는 Thread 클래스를 통해 다중 스레드를 생성할 수 있습니다. Ruby의 스레드는 가벼운 중량의 스레드로, 병行的 코드를 효율적으로 구현할 수 있습니다.

Ruby 스레드 생성

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

# 스레드 #1 코드 부분
Thread.new {
  # 스레드 #2 코드 실행
}
# 스레드 #1 코드 실행

온라인 예제

다음 예제는 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}"
t1Thread.new{func1()}
t2Thread.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와 같은 동일한 문법으로 Thread.new, Thread.start, Thread.fork를 사용할 수 있습니다.

2스레드를 생성한 후에는 시작하지 않아도 됩니다. 스레드는 자동으로 실행됩니다.

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

4스레드 코드 블록의 마지막 문장은 스레드의 값이며, 스레드 메서드를 통해 호출할 수 있습니다. 스레드가 실행되면 스레드 값이 반환되며, 스레드가 실행되지 않으면 값을 반환하지 않습니다.

5Thread.current 메서드는 현재 스레드를 나타내는 객체를 반환합니다. Thread.main 메서드는 메인 스레드를 반환합니다.

6Thread.Join 메서드를 통해 스레드를 실행하면, 이 메서드는 메인 스레드를 대기시키고 현재 스레드가 완료될 때까지 대기합니다.

스레드 상태

스레드는5다음과 같은 상태가 있습니다:

스레드 상태반환 값
실행가능run
수면Sleeping
종료aborting
정상 종료false
예외로 인한 종료nil

스레드와 예외

특정 스레드가 예외가 발생했지만 rescue로 잡히지 않았을 때, 이 스레드는 일반적으로 경고 없이 종료됩니다. 하지만 다른 스레드가 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를 통해 지정된 스레드에 표지를 설정합니다.

위와 같이 사용할 때3이 방법 중 하나가 선택되면 전체 인터프리터가 중지됩니다.

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

스레드 동기화 제어

Ruby에서는 동기화를 구현하는 세 가지 방법을 제공합니다.

1. Mutex 클래스를 통해 스레드 동기화 구현

2. 데이터 전달을 모니터링하는 Queue 클래스를 통해 스레드 동기화 구현

3. ConditionVariable를 사용하여 동기화 제어

Mutex 클래스를 통해 스레드 동기화를 구현

Mutex 클래스를 통해 스레드 동기화 제어를 구현합니다. 여러 스레드가 동시에 특정 프로그램 변수를 필요로 할 때, 이 변수의 일부를 lock으로 잠금할 수 있습니다. 코드는 다음과 같습니다:

온라인 예제

#!/usr/bin/ruby
 
require "thread"
puts "동기화 스레드"
 
@num=200
@mutex=Mutex.new
 
def buyTicket(num)
     @mutex.lock
          if @num>=num
               @num=@num-num
               puts "귀하가 성공적으로 #{num} 티켓을 구매했습니다"
          else
               puts "죄송합니다, 티켓이 충분하지 않습니다"
          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

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

동기화 스레드
귀하가 성공적으로 구매했습니다 15 tickets
귀하가 성공적으로 구매했습니다 20 티켓
귀하가 성공적으로 구매했습니다 15 tickets
귀하가 성공적으로 구매했습니다 20 티켓
귀하가 성공적으로 구매했습니다 15 tickets
귀하가 성공적으로 구매했습니다 20 티켓
귀하가 성공적으로 구매했습니다 15 tickets
귀하가 성공적으로 구매했습니다 20 티켓
귀하가 성공적으로 구매했습니다 15 tickets
귀하가 성공적으로 구매했습니다 20 티켓
귀하가 성공적으로 구매했습니다 15 tickets
죄송합니다, 티켓이 충분하지 않습니다
죄송합니다, 티켓이 충분하지 않습니다
죄송합니다, 티켓이 충분하지 않습니다
죄송합니다, 티켓이 충분하지 않습니다
죄송합니다, 티켓이 충분하지 않습니다
죄송합니다, 티켓이 충분하지 않습니다
죄송합니다, 티켓이 충분하지 않습니다
죄송합니다, 티켓이 충분하지 않습니다
죄송합니다, 티켓이 충분하지 않습니다

lock 변수를 사용하는 것 외에도 try_lock 변수를 사용할 수 있으며, 또한 Mutex.synchronize를 사용하여 특정 변수에 대한 접근을 동기화할 수 있습니다.

데이터 전달을 모니터링하는 Queue 클래스는 스레드 동기화를 구현

Queue 클래스는 스레드를 지원하는 큐를 나타내며, 큐의 마지막에 대한 동기화된 접근을 제공합니다. 다른 스레드는 동일한 클래스를 사용할 수 있지만, 이 큐에서의 데이터가 동기화되는지에 대해 걱정하지 않아도 됩니다. 또한, SizedQueue 클래스를 사용하여 큐의 길이를 제한할 수 있습니다.

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 테스트
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

Thread 변수

Thread는 자신의 비밀 변수를 가질 수 있으며, Thread의 비밀 변수는 Thread가 생성될 때 입력됩니다. Thread 범위 내에서 사용할 수 있지만, Thread 외부에서 공유되지 않습니다.

하지만, 때로는 Thread의 지역 변수가 다른 Thread나 메인 Thread에 의해 접근되어야 할 때는 어떻게 해야 하나요? Ruby는 이름을 통해 Thread 변수를 생성할 수 있으며, Thread를 Hash 스타일의 해시 테이블로 볼 수 있습니다. []=를 통해 데이터를 입력하고, []를 통해 데이터를 출력할 수 있습니다. 아래의 코드를 보겠습니다:

온라인 예제

#!/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

메인 Thread는 서브 Thread가 실행되기를 기다리고, 각 값을 나누어 출력합니다. 。

Thread prioritiy

Thread의 prioritiy는 Thread의 스케줄링에 영향을 미치는 주요 요소입니다. 다른 요소로는 CPU 사용 시간의 길이, Thread 그룹 스케줄링 등이 있습니다.

Thread.priority 메서드를 사용하여 Thread의 prioritiy를 얻을 수 있으며, Thread.priority= 메서드를 사용하여 prioritiy를 조정할 수 있습니다.

Thread의 기본 prioritiy는 0입니다. prioritiy가 높은 Thread는 더 빠르게 실행됩니다.

한 Thread는 자신의 범위 내의 모든 데이터에 접근할 수 있지만, 특정 Thread 내에서 다른 Thread의 데이터에 접근할 필요가 있을 때 어떻게 해야 하나요? Thread 클래스는 Thread 데이터 간의相互 접근 방법을 제공하며, 단순히 한 Thread를 Hash 테이블로 사용하여 데이터를 입력하거나 출력할 수 있습니다.

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를 해시 테이블로 사용하여 []와 []= 메서드를 통해 스레드 간의 데이터 공유를 구현할 수 있습니다.

스레드互斥

Mutex( Mutual Exclusion =互斥锁)는 다중 스레드 프로그래밍에서, 두 개의 스레드가 동시에 동일한 공동 자원(예: 전역 변수)을 읽고 쓰지 못하도록 하는 메커니즘입니다.

Mutax 사용하지 않은 예제

온라인 예제

#!/usr/bin/ruby
require 'thread'
 
count1 =2 =
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

mutex의 사용 예제

온라인 예제

#!/usr/bin/ruby
require 'thread'
mutex = Mutex.new
 
count1 =2 =
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

死锁

두 개 이상의 연산 단위가 상호간에 상대방이 실행을 중지하도록 기다리며 시스템 자원을 얻으려고 하지만 어떤一方도 미리 탈출하지 않으면서 이 상태는死锁이라고 합니다.

예를 들어, 프로세스 p1디스플레이어를 사용하면서 또한 프린터를 사용해야 하지만 프린터는 프로세스 p에 의해 사용되고 있습니다.2占用,p2또한 디스플레이어를 사용해야 하므로死锁이 발생합니다.

Mutex 객체를 사용할 때는死锁에 주의해야 합니다.

온라인 예제

#!/usr/bin/ruby
require 'thread'
mutex = Mutex.new
 
cv = ConditionVariable.new
a = Thread.new {
   mutex.synchronize {
      puts "A: 중요 구간을 가지고 있지만 cv를 기다리겠습니다"
      cv.wait(mutex)
      puts "A: 중요 구간을 다시 얻었습니다! 제가 통치합니다!"
   }
}
 
puts "(후에, 랜치로 돌아옴...)"
 
b = Thread.new {
   mutex.synchronize {
      puts "B: 지금 저는 중요한 섹션입니다, cv와는 끝내었습니다"
      cv.signal
      puts "B: 저는 여전히 중요한 섹션입니다, 완료하고 있습니다"
   }
}
a.join
b.join

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

A: 저는 중요한 섹션을 가지고 있지만 cv를 기다릴 것입니다
(나중에, 농장으로 돌아와서...)
B: 지금 저는 중요한 섹션입니다, cv와는 끝내었습니다
B: 저는 여전히 중요한 섹션입니다, 완료하고 있습니다
A: 저는 다시 중요한 섹션을 가지고 있습니다! 저는 통치합니다!

스레드 클래스 메서드

Thread(스레드) 클래스의 메서드는 다음과 같습니다:

순번메서드 설명
1Thread.abort_on_exception
값이 참이면, 어떤 스레드가 예외로 인해 종료되면 전체 해석기가 중지됩니다. 기본 값은 거짓으로, 즉 일반적으로 어떤 스레드가 예외가 발생했지만 이 예외가 Thread#join 등에 의해 검출되지 않았을 때, 그 스레드는 경고 없이 종료됩니다.
2Thread.abort_on_exception=
만약 설정되면 true어떤 스레드가 예외로 인해 종료되면, 전체 해석기가 중지됩니다. 새로운 상태를 반환합니다.
3Thread.critical
부울 값을 반환합니다.
4Thread.critical=
값이 true일 때, 스레드 전환은 수행되지 않습니다. 현재 스레드가 중지(stop) 상태이거나 신호(signal)이 중간에 개입할 때, 값이 자동으로 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과 일치하는 스레드固有 데이터를 꺼냅니다. name은 문자열이나 기호일 수 있습니다. name과 일치하는 데이터가 없을 경우 nil을 반환합니다.
2thr[ name ]=
스레드 내에서 name과 일치하는 스레드固有 데이터의 값을 설정합니다. name은 문자열이나 기호일 수 있습니다. nil로 설정하면, 해당 스레드 내에서 해당 데이터를 제거합니다.
3thr.abort_on_exception
부울 값을 반환합니다.
4thr.abort_on_exception=
이 값이 true라면, 어떤 스레드가 예외로 종료되면 전체 인터프리터가 중지됩니다.
5thr.alive?
스레드가 "활성"인 경우 true를 반환합니다.
6thr.exit
스레드의 실행을 종료합니다. self을 반환합니다.
7thr.join
현재 스레드를 일시적으로 중지하고, self 스레드가 실행을 종료할 때까지 기다립니다. self이 예외로 종료되면 현재 스레드에도 같은 예외가 발생합니다。
8thr.key?
name과 일치하는 스레드固有 데이터가 이미 정의되어 있다면 true를 반환합니다
9thr.kill
유사합니다 Thread.exit
10thr.priority
스레드의 우선순위를 반환합니다. 기본 우선순위 값은 0입니다. 이 값이 클수록 우선순위가 높습니다.
11thr.priority=
스레드의 우선순위를 설정합니다. 음수를 설정할 수도 있습니다.
12thr.raise( anException )
이 스레드 내에서 강제로 예외를 발생시킵니다.
13thr.run
挂起的(stop)线程重新启动. wakeup와 달리, 즉시 스레드 전환을 수행합니다. 사망한 프로세스에 대해 이 메서드를 사용할 경우 ThreadError 예외가 발생합니다.
14thr.safe_level
self의 보안 수준을 반환합니다. 현재 스레드의 safe_level은 $SAFE와 같습니다.
15thr.status
활성 스레드의 상태를 나타내기 위해 문자열 "run", "sleep" 또는 "aborting"을 사용합니다. 스레드가 정상적으로 종료된 경우 false를 반환합니다. 예외로 종료된 경우 nil을 반환합니다.
16thr.stop?
스레드가 종료 상태(dead)이거나挂起(stop) 상태이면 true를 반환합니다.
17thr.value
self 스레드가 종료될 때까지 기다리며, 그 후 스레드 블록의 반환 값을 반환합니다. 스레드의 실행 중에 예외가 발생하면 해당 예외가 다시 발생합니다.
18thr.wakeup
挂起(stop)된 스레드의 상태를 실행 가능한 상태(run)으로 변경합니다. 이 메서드를 사망 스레드에 대해 호출할 경우 ThreadError 예외가 발생합니다.