MULTi THREAD iN RUBY

HyoKwangRyu·2020년 9월 16일
3

Ruby on Rails

목록 보기
3/6
post-thumbnail

광고
더치페이 대신 랜덤빵 하십쇼🚀
iOS 앱 랜덤빵
다운로드 부탁 더립니다..

Green Thread vs Native Thread

루비의 스레드에 대해 이야기 하기 전에.

Green Thread

Many(Thread) to One(CPU) 모델로 설계된 스레드.

VM이나 Lribrary등 에서 개발자가 통제 가능하다.
그린 스레드가 아무리 많아도 네이티브 스레드는 단 한개만 생성된다.
→ CPU 하나만 사용한다.

멀티 코어 CPU 환경의 장점을 살리지 못함.

하지만 장점이 있다.

싱글 코어 환경에서 10 Native thread VS 10 green thread 에 대해 생각해 보자.

네이티브 10개 사용 → 네이티브 10개 생성
그린 10개 사용 → 네이티브 1개 생성

→ CPU의 입장에서는 네이티브 10개 쓰는것 보다 1개쓰는게 성능이 좋다.
why? 네이티브 스레드가 1개 뿐이기 때문에, 동기화 할 공유 자원이 없다.

그래서 싱글코어, 싱글 스레드 환경에서는 그린 스레드가 성능이 더 좋다.

Native Thread

진짜 스레드.
OS단에서 관리된다.
non-green, kernel-level thread 라고도 함.

Many to Many 모델로 설계됨.

멀티 코어 환경에서 강점이 있다.

하지만,
여러 스레드를 사용 한다면
→ 스레드 사이에 공유되는 자원을 동기화 해야하는 이슈가 있다.
→ 그린 스레드에 비해 복잡한 동기화 문제가 있다.

동기화를 잘 하면 된다ㅎㅎ

In Ruby.

ruby 1.8.7 까지는 그린스레드 방식이었음.
ruby 1.9 이후로 네이티브 OS 스레드를 사용함. 하지만 멀티 코어 환경이어도 싱글 프로세스 속에서만 동작함.

참고사항.
각 HTTP 요청이 별도의 스레드에서 처리 될 수 있고, 요청간에 많은 리소스를 공유하지 않기 때문에 스레드는 대부분의 경우 잘 작동합니다.

Mutex

아래 코드에서는 race condition 이 발생한다

@count = 0

def read_count
  @count
end

def add_value value
  @count = value
end

100.times.map do 
  Thread.new do
    100_000.times do
      value = read_count
      value = value + 1
      add_value value
    end
  end
end.each(&:join)

puts @count

# 4214508

컨텍스트 스위칭이 일어나기 떄문.
루비에서는 메소드를 호출 하거나, 메소드로부터 리턴 될때 컨텍스트 스위칭이 일어남.

스레드는 컨텍스트를 변경한다.
스레드가 멈추면, 그 상태와 컨텍스트가 저장되고 다른 스레드가 CPU 사이클을 사용할 수 있게 된다.

@count = 0

100.times.map do 
  Thread.new do
    100_000.times do
      value = @count
      value = value + 1
      @count = value
    end
  end
end.each(&:join)

puts @count

# 10000000

이렇게 하면 컨텍스트 스위칭이 일어나지 않고, 경쟁 상태도 없다.
하지만 대부분읭 경우에 메소드호출을 많이 할 수 밖에 없다.

컨텍스트 스위칭이 일어나면서 공유자원의 동시 사용을 막기 위해서는?
뮤텍스 세마포 등을 이용해야 한다.

@count = 0
mutex = Mutex.new

def read_count
  @count
end

def add_value value
  @count = value
end

100.times.map do
  Thread.new do
    100_000.times do
      mutex.synchronize do
        value = read_count
        value = value + 1
        add_value value
      end
    end
  end
end.each(&:join)

puts @count

# 10000000

뮤텍스를 이용한 코드

뮤텍스를 통해 공유 자원에 락을 걸면서 상호 배제를 만들어 낸다.

뮤텍스는 단 하나의 스레드만 공유자원에 접근 가능하게 한다. 다른 스레드들은 공유자원에 접근중인 스레드가 작업이 끝날때 까지 기다린다(블락됨).

이러한 특성 때문에 뮤텍스는 공유 자원의 원자성을 보장하기 위해 사용한다.

멀티 스레드 프로그래밍은 두개 이상의 스레드가 동시 연산을 하면서 프로세스(소프트웨어)의 성능을 높이기 위해 사용한다.

그런데 뮤텍스는 락을 걸기 때문에, 잘 사용하지 않으면 병목구간이 될 수 있다.
가능하면 뮤텍스 구간을 최소화 하는게 좋다.

실행시간을 대충 측정해봤다.

0.618842 메소드 없이
0.975594 메소드 콜
51.195683 뮤텍스 사용
0.975366 뮤텍스 블럭을 100_100.time 블럭 안으로 이동

뮤텍스 락을 적절한 위치에서 사용하면 성능을 높일 수 있다.

Condition Variable

조건에 따라 뮤텍스 락과 언락을 수행하는 방법.

why? 공유자원을 관리가 뮤텍스로 충분하지 않을 수 있다.

뮤텍스를 얻고 작업을 진행하는 중, 다른 스레드의 자원이 준비 되는걸 기다려야 할 때가 있다. 준비 될때까지 다른 스레드들은 블락될 것이다.

이때, Condition Variable(CV)을 사용한다.

사용방식
→ 한 스레드가 뮤텍스를 얻고 작업을 진행
→ 다른 자원을 기다리기 전, 뮤텍스 반환
→ 자원을 얻으면, 다시 뮤텍스를 얻음(이때, 다른 스레드와 경쟁하기 때문에, 일정 시간 대기 상태에 있을 수 있다.)
→ 작업 끝나면, 뮤텍스 반환

mutex = Mutex.new
cv = ConditionVariable.new

count = 0

a = Thread.new {
  mutex.synchronize {
    puts "A: #{count}"
    cv.wait(mutex) if count == 0
    puts "A: #{count}"
  }
}

b = Thread.new {
  mutex.synchronize {
    puts "B: #{count}"
    count += 1
    cv.signal
    puts "B: #{count}"
  }
}
[a, b].each(&:join)

# A: 0
# B: 0
# B: 1
# A: 1

그 외 메소드들

Thread.kill

스레드를 종료함

t = Thread.new { ... }
Thread.kill(t)
# t.exit과 동일

Thread.status

스레드의 상태를 리턴

t = Thread.new { sleep }
t.status # => "sleep"
t.exit
t.status # => false

Thread.current

현재 실행 중인 스레드를 리턴

Thread.current #=> #<Thread:0x401bdf4c run>

참고 - https://ruby-doc.org/core-2.5.0/Thread.html

한번더

더치페이 대신 랜덤빵 하십쇼🚀
iOS 앱 랜덤빵
다운로드 버탁 더립니더..

profile
Backend Developer

2개의 댓글

comment-user-thumbnail
2021년 5월 26일

좋은포스팅 감사합니다~!

1개의 답글