Thread
Thread (스레드)
Thread는 Ruby의 동시성(concurrent) 프로그래밍 모델을 구현한 클래스예요. 여러 스레드(병렬 실행 단위)가 필요한 프로그램에 딱 맞는 도구랍니다.
출처: Ruby 3.3 API
본문
메인 스레드와는 별개의 새 스레드를 ::new로 만들 수 있어요.
thr = Thread.new { puts "What's the big deal" }
그리고 join으로 메인 스레드의 실행을 잠시 멈추고 새 스레드가 끝나길 기다릴 수 있어요.
thr.join #=> "What's the big deal"
메인 스레드가 종료되기 전에 thr.join을 호출하지 않으면, thr를 포함한 다른 모든 스레드가 종료돼요.
배열로 여러 스레드를 한 번에 다룰 수도 있어요.
threads = []
threads << Thread.new { puts "What's the big deal" }
threads << Thread.new { 3.times { puts "Threads are fun!" } }
threads.each { |thr| thr.join }
스레드의 마지막 값을 얻으려면 value를 사용해요.
thr = Thread.new { sleep 1; "Useful value" }
thr.value #=> "Useful value"
스레드 초기화
새 스레드를 만들기 위해 Ruby는 ::new, ::start, ::fork를 제공해요. 각 메서드에 반드시 블록을 주어야 하며, 주지 않으면 ThreadError가 발생해요.
Thread 클래스를 서브클래싱할 때, 서브클래스의 initialize 메서드는 ::start와 ::fork에서 무시돼요. 그 외에는 initialize에서 반드시 super를 호출하세요.
스레드 종료
스레드를 종료하는 방법은 여러 가지가 있어요. 클래스 메서드 ::kill은 주어진 스레드를 종료해요.
thr = Thread.new { sleep }
Thread.kill(thr) # sends exit() to thr
또는 인스턴스 메서드 exit(별칭 kill, terminate)을 사용할 수 있어요.
thr.exit
스레드 상태
status로 현재 스레드 상태를 나타내는 문자열을 얻을 수 있어요.
thr = Thread.new { sleep }
thr.status # => "sleep"
thr.exit
thr.status # => false
alive?로 스레드가 실행 중이거나 잠들어 있는지, stop?으로 죽었거나 잠들어 있는지를 알 수 있어요.
스레드 변수와 스코프
스레드는 블록으로 만들기 때문에 일반 Ruby 블록과 같은 스코프 규칙이 적용돼요. 블록 안에서 만든 지역 변수는 그 스레드에서만 접근 가능해요.
Fiber-local vs Thread-local
각 fiber는 Thread#[] 저장소를 위한 자체 버킷을 가져요. 새 fiber-local을 설정하면 그 Fiber 안에서만 접근할 수 있어요.
Thread.new {
Thread.current[:foo] = "bar"
Fiber.new {
p Thread.current[:foo] # => nil
}.resume
}.join
이 예시는 fiber-local을 가져오기 위해 []를, 설정하기 위해 []=를 썼어요. keys로 스레드의 fiber-local을 나열하고, key?로 fiber-local 존재 여부를 확인할 수 있어요.
스레드-local(thread-local)은 스레드 전체 스코프에서 접근 가능해요.
Thread.new {
Thread.current.thread_variable_set(:foo, 1)
p Thread.current.thread_variable_get(:foo) # => 1
Fiber.new {
Thread.current.thread_variable_set(:foo, 2)
p Thread.current.thread_variable_get(:foo) # => 2
}.resume
p Thread.current.thread_variable_get(:foo) # => 2
}.join
thread-local인 :foo가 fiber 안으로 이어졌다가(fiber-local과 달리), 스레드 끝에 가서는 2로 바뀐 걸 볼 수 있어요.
클래스 메서드 (Public Class Methods)
abort_on_exception → true or false- 전역 "abort on exception" 조건의 상태를 돌려줘요. 기본값은false.true로 설정하면 어떤 스레드가 예외로 중단됐을 때 그 예외가 메인 스레드에서 다시 던져져요.$DEBUG플래그로도 지정 가능해요.abort_on_exception= boolean → true or false-true로 설정하면 예외로 중단된 스레드의 예외가 메인 스레드에서 다시 던져져요. 새 상태를 돌려줘요.current → thread- 현재 실행 중인 스레드를 돌려줘요.Thread.current #=> #<Thread:0x... run>each_caller_location{ |loc| ... } → nil- 현재 실행 스택의 각 프레임을 backtrace location 객체로 yield해요.exit → thread- 현재 실행 중인 스레드를 종료하고 다른 스레드의 실행을 예약해요. 이미 kill 표시된 스레드면::exit는 그Thread를 돌려줘요. 메인 스레드나 마지막 스레드면 프로세스를 종료해요.start([args]*) {|args| block } → thread/fork- 기본적으로::new와 같아요. 다만Thread를 서브클래싱한 경우 서브클래스에서start를 호출해도 그 서브클래스의initialize메서드를 호출하지 않아요.handle_interrupt(hash) { ... } → result of the block- 비동기 인터럽트 타이밍을 변경해요.Thread#raise,Thread#kill, 시그널 trap, 메인 스레드 종료 같은 비동기 이벤트의 처리를 조정해요.ignore_deadlock → true or false- 전역 "ignore deadlock" 조건의 상태를 돌려줘요. 기본값은false라 데드락 조건을 무시하지 않아요.ignore_deadlock= boolean → true or false- VM이 데드락 조건을 검사하지 않게 해요. 시그널 같은 다른 수단으로 데드락을 풀 수 있을 때만 유용해요.kill(thread) → thread- 주어진 스레드를 종료시켜요.Thread.exit참고.list → array- 실행 가능(runnable)하거나 정지된 모든 스레드의 배열을 돌려줘요.main → thread- 메인 스레드를 돌려줘요.new { ... } → thread- 주어진 블록을 실행하는 새 스레드를 만들어요.::new에 준 인자는 블록에 전달돼요.new(*args, &proc) → thread-::new에&proc(Proc)을 주면 그 Proc을 실행하는 스레드를 만들어요.new(*args) { |args| block } → thread- 인자 없이
::new를 호출하면ThreadError가 발생해요.
pass → nil- 스레드 스케줄러에게 다른 스레드로 실행을 넘기라는 힌트를 줘요. 실행 중인 스레드가 실제로 바뀔지는 OS/프로세서에 따라 달라요.pending_interrupt?(error = nil) → true/false- 비동기 큐가 비어 있는지 여부를 돌려줘요. 지연된 이벤트가 있는지 확인하는 데 써요.report_on_exception → true or false- 전역 "report on exception" 조건 상태. 기본값은 Ruby 2.5부터true예요. 스레드를 예외로 죽이면$stderr에 메시지를 보고해요.report_on_exception= boolean → true or false-true로 설정하면 그 뒤에 만들어진 모든 스레드가 상속받아 예외로 죽을 때$stderr에 보고해요.stop → nil- 현재 스레드 실행을 멈추고 "sleep" 상태로 만들어 다른 스레드 실행을 예약해요.
a = Thread.new { print "a"; Thread.stop; print "c" }
sleep 0.1 while a.status != 'sleep'
print "b"
a.run
a.join #=> "abc"
인스턴스 메서드 (Public Instance Methods)
thr[sym] → obj or nil- fiber-local 변수 값을 돌려줘요. (명시적으로 Fiber 안이 아니면 현재 스레드의 루트 fiber) 존재하지 않으면nil.thr[sym] = obj → obj- fiber-local 변수 값을 설정/생성해요. thread-local은thread_variable_set/thread_variable_get사용.abort_on_exception → true or false/abort_on_exception= boolean- 이 스레드의 로컬 "abort on exception" 상태.add_trace_func(proc) → proc-proc을 추적 핸들러로 추가해요. (Thread#set_trace_func,Kernel#set_trace_func참고)alive? → true or false-thr가 실행 중이거나 잠들어 있으면true를 돌려줘요.stop?과status참고.backtrace → array or nil- 대상 스레드의 현재 backtrace를 돌려줘요.backtrace_locations(*args) → array or nil- 대상 스레드의 실행 스택을 backtrace location 객체 배열로 돌려줘요.Kernel#caller_locations처럼 동작하되 특정 스레드에 적용돼요.exit → thr(terminate,kill별칭) -thr를 종료하고 다른 스레드 실행을 예약해요.fetch(sym) → obj- 키에 해당하는 fiber-local을 돌려줘요. 없으면 다른 옵션: 인자 없이KeyError,default가 있으면 그것, 블록이 있으면 블록 결과.group → thgrp or nil- 스레드를 담은ThreadGroup을 돌려줘요.Thread.main.group #=> #<ThreadGroup:...>join → thr/join(limit) → thr- 호출 스레드는 실행을 멈추고 이thr을 실행해요.thr이 끝나거나limit초가 지날 때까지 반환하지 않아요. 제한 시간이 지나면nil을 돌려줘요. join하지 않은 스레드는 메인 스레드 종료 시 모두 종료돼요.key?(sym) → true or false- 주어진 문자열(또는 심볼)이 fiber-local 변수로 존재하면true.keys → array- fiber-local 변수 이름(심볼)들의 배열.kill → thr-thr를 종료하고 다른 스레드 실행을 예약해요.name → string- 스레드 이름을 보여줘요.name=(name)- 이름을 설정해요 (일부 플랫폼에선 pthread/커널에도 설정).native_thread_id → integer- Ruby 스레드가 쓰는 네이티브 스레드 ID. OS에 따라 달라요. Linux에선gettid(2)의 TID, macOS에선pthread_threadid_np의 시스템 고유 ID.pending_interrupt?(error = nil) → true/false- 대상 스레드의 비동기 큐가 비어 있는지.priority → integer/priority= integer → thr- 스레드 우선순위. 우선순위가 높은 스레드가 더 자주 실행돼요(낮은 스레드도 실행될 수 있음). Ruby 스케줄러에 대한 힌트일 뿐 플랫폼에 따라 무시될 수 있어요.raise- 주어진 스레드에서 예외를 던져요. 호출자는thr일 필요 없어요.raise(string),raise(exception [, string [, array]])형태.
Thread.abort_on_exception = true
a = Thread.new { sleep 200 }
a.raise("Gotcha")
report_on_exception → true or false/report_on_exception= boolean- 이 스레드의 로컬 "report on exception" 상태.run → thr-thr을 깨워 스케줄링 대상으로 만들어요.
a = Thread.new { puts "a"; Thread.stop; puts "c" }
sleep 0.1 while a.status != 'sleep'
puts "Got here"
a.run
a.join
# a
# Got here
# c
set_trace_func(proc) → proc/set_trace_func(nil) → nil-thr의 추적 핸들러를 설정하거나(nil이면) 추적을 비활성화해요.status → string, false or nil- 스레드 상태를 돌려줘요."sleep"- 잠들거나 I/O를 기다리는 중"run"- 실행 중"aborting"- 중단되는 중false- 정상 종료됨nil- 예외로 종료됨
stop? → true or false-thr이 죽었거나 잠들어 있으면true.thread_variable?(key) → true or false- thread-local 변수가 존재하면true.thread_variable_get(key) → obj or nil- 설정된 thread-local 변수 값을 돌려줘요.thread_variable_set(key, value)-key를value로 설정해요.thread_variables → array- thread-local 변수 이름(심볼) 배열.to_s → string(inspect별칭) - 이름, id, 상태를 문자열로 덤프해요.value → obj-join을 써서thr이 끝나길 기다리고, 그 값 또는 스레드를 종료시킨 예외를 돌려줘요.
a = Thread.new { 2 + 2 }
a.value # => 4
b = Thread.new { raise 'something went wrong' }
b.value # => RuntimeError: something went wrong
wakeup → thr- 주어진 스레드를 스케줄링 대상으로 표시해요. 단 I/O에는 여전히 블록될 수 있어요. 스케줄러를 호출하지는 않아요(run참고).
c = Thread.new { Thread.stop; puts "hey!" }
sleep 0.1 while c.status != 'sleep'
c.wakeup
c.join #=> "hey!"
더 알아보기
Thread::Backtrace::Location(backtrace 위치 정보), Thread::Mutex, Thread::Queue, Thread::SizedQueue, Thread::ConditionVariable 등 관련 클래스와, Kernel#set_trace_func을 함께 보면 도움이 돼요.