Ruby의 스레드란 무엇입니까?
스레드는 Ruby 프로그램이 동시에 여러 작업을 수행하도록 합니다.
예: :
- 여러 파일 읽기
- 여러 웹 요청 처리
- 여러 API 연결 만들기
스레드를 사용하면 작업을 더 빠르게 완료할 수 있는 다중 스레드 Ruby 프로그램을 갖게 됩니다.
하지만 하나의 경고...
Ruby 애플리케이션을 실행하는 기본 방법인 MRI(Matz의 Ruby Interpreter)에서는 i/o 바운드 애플리케이션을 실행할 때만 스레드의 이점을 누릴 수 있습니다. .
이 제한은 GIL(Global Interpreter Lock) 때문에 존재합니다. .
JRuby 또는 Rubinius와 같은 대체 Ruby 인터프리터는 멀티스레딩을 최대한 활용합니다.
스레드란 무엇인가요?
스레드는 작업자 또는 실행 단위입니다.
모든 프로세스에는 하나 이상의 스레드가 있으며 요청 시 추가로 생성할 수 있습니다.
코드 예제를 보고 싶어한다는 것을 알고 있습니다.
하지만 먼저 CPU 바운드 애플리케이션과 I/O 바운드 애플리케이션의 차이점에 대해 이야기해야 합니다.
I/O 바인딩된 애플리케이션
i/o 바인딩된 앱 외부 리소스를 기다려야 하는 것입니다.
- API 요청
- 데이터베이스(쿼리 결과)
- 디스크 읽기
스레드는 리소스를 사용할 수 있을 때까지 기다리는 동안 중지를 결정할 수 있습니다. 즉, 다른 스레드가 실행되어 해당 작업을 수행하고 대기 시간을 낭비하지 않을 수 있습니다.
i/o 바운드 앱의 한 예 웹 크롤러입니다.
모든 요청에 대해 크롤러는 서버가 응답할 때까지 기다려야 하며 기다리는 동안에는 아무 것도 할 수 없습니다.
하지만 쓰레드를 사용한다면...
한 번에 4개의 요청을 수행하고 응답이 돌아올 때 처리할 수 있으므로 페이지를 더 빨리 가져올 수 있습니다.
이제 코드 예제를 작성할 차례입니다.
루비 스레드 생성
Thread.new
를 호출하여 새로운 Ruby 스레드를 생성할 수 있습니다. .
이 스레드를 실행하는 데 필요한 코드가 포함된 블록을 전달해야 합니다.
Thread.new { puts "hello from thread" }
꽤 쉽죠?
그러나.
다음 코드가 있는 경우 스레드에서 출력이 없음을 알 수 있습니다.
t = Thread.new { puts 10**10 } puts "hello"
문제는 Ruby가 스레드가 끝날 때까지 기다리지 않는다는 것입니다.
join
을 호출해야 합니다. 위의 코드를 수정하려면 스레드에서 메서드를 사용하세요.
t = Thread.new { puts 10**10 } puts "hello" t.join
여러 스레드를 만들고 싶다면 배열 안에 넣고 join
을 호출하면 됩니다. 모든 스레드에서.
예 :
threads = [] 10.times { threads << Thread.new { puts 1 } } threads.each(&:join)
Ruby 스레드를 탐색하는 동안 유용한 문서를 찾을 수 있습니다.
https://ruby-doc.org/core-2.5.0/Thread.html
스레드 및 예외
스레드 내부에서 예외가 발생하면 프로그램을 중지하거나 어떤 종류의 오류 메시지도 표시하지 않고 조용히 종료됩니다.
다음은 예입니다:
Thread.new { raise 'hell' }
디버깅 목적으로 나쁜 일이 발생하면 프로그램이 중지되기를 원할 수 있습니다. 이를 위해 Thread
에 다음 플래그를 설정할 수 있습니다. 참으로:
Thread.abort_on_exception = true
스레드를 생성하기 전에 이 플래그를 설정해야 합니다. 🙂
스레드 풀
처리할 항목이 수백 개 있다고 가정해 보겠습니다. 각 항목에 대해 스레드를 시작하면 시스템 리소스가 파괴됩니다.
다음과 같이 보일 것입니다:
pages_to_crawl = %w( index about contact ... ) pages_to_crawl.each do |page| Thread.new { puts page } end
이렇게 하면 서버에 대해 수백 개의 연결을 시작하게 되므로 좋은 생각이 아닐 것입니다.
한 가지 해결책은 스레드 풀을 사용하는 것입니다.
스레드 풀을 사용하면 주어진 시간에 활성 스레드 수를 제어할 수 있습니다.
자신의 수영장을 만들 수는 있지만 권장하지는 않습니다. 다음 예에서는 셀룰로이드 보석을 사용하여 이 작업을 수행합니다.
<블록 인용>참고:셀룰로이드는 이제 유지 관리되지 않지만 작업자 풀의 일반적인 개념은 여전히 적용됩니다.
require 'celluloid' class Worker include Celluloid def process_page(url) puts url end end pages_to_crawl = %w( index about contact products ... ) worker_pool = Worker.pool(size: 5) # If you need to collect the return values check out 'futures' pages_to_crawl.each do |page| worker_pool.process_page(page) end
이번에는 5개의 스레드만 실행되고 완료되면 다음 항목을 선택합니다.
경주 조건 및 기타 위험
이것은 매우 멋지게 들릴지 모르지만 코드 전체에 스레드를 뿌리기 전에 동시 코드와 관련된 몇 가지 문제가 있음을 알아야 합니다.
예를 들어 스레드는 경쟁 조건에 취약합니다.
경합 조건 일이 잘못되어 엉망이 될 때입니다.
발생할 수 있는 또 다른 문제는 교착 상태입니다. 이것은 한 스레드가 일부 리소스에 대한 독점적 액세스(뮤텍스와 같은 잠금 시스템 사용)를 보유하고 이를 해제하지 않아 다른 모든 스레드에서 액세스할 수 없는 경우입니다.
이러한 문제를 피하려면 원시 스레드를 피하고 이미 세부 사항을 처리하는 일부 gem을 사용하는 것이 가장 좋습니다.
더 많은 스레딩 보석
우리는 이미 스레드 풀에 셀룰로이드를 사용했지만 확인해야 할 다른 동시성 중심 보석이 많이 있습니다.
- https://github.com/grosser/parallel
- https://github.com/chadrem/workers
- https://github.com/ruby-concurrency/concurrent-ruby
알겠습니다. Ruby 스레드에 대해 한두 가지 배웠기를 바랍니다. !
이 기사가 유용했다면 공유하세요. 친구들도 배울 수 있도록 🙂