자바 병렬프로그래밍[1]

kimji1
5 min readMar 10, 2020

--

스레드를 가벼운 프로세스라고 부르기도 하며, 현대 운영체제의 대부분은 프로세스가 아니라 스레드를 기본 단위로 CPU 자원의 스케줄을 정한다. 의도적으로 조율하지 않는 한 하나의 스레드는 자신이 포함된 프로세스의 메모리 주소 공간을 공유하기 때문에 하나 프로세스 내 모든 스레드는 같은 변수에 접근하고 같은 힙에 객체를 할당한다. 이 때문에 프로세스 때보다 더 세밀한 단위로 데이터를 공유할 수 있다. 하지만 공유된 데이터에 접근하는 과정을 적절하게 동기화하지 않으면 다른 스레드가 사용 중인 변수를 순간적으로 수정해서 예상치 못한 결과를 얻을 수 있다.

프로세서 스케줄링의 기본 단위는 스레드이기 때문에 스레드 하나로 동작하는 프로그램은 한 번에 최대 하나의 프로세서만 사용한다. 반면 활성 상태인 스레드가 여러 개인 프로그램은 여러 프로세서에서 동시에 실행될 수 있다. 여러 개의 스레드를 사용하면 프로세서가 하나라 해도 처리 속도를 높일 수 있다. 프로그램이 스레드 하나로 구성되면 동기 I/O 작업이 완료될 때까지 기다리는 동안 프로세서가 놀게 된다. 멀티스레드 프로그램에선 스레드 하나가 I/O가 끝나길 기다리는 동안 다른 스레드가 계속 실행될 수 있다. 즉 I/O 때문에 대기 상태에 들어가는 동안에도 다른 스레드는 동작할 수 있기 때문에 애플리케이션이 계속 실행된다.

스레드 사용의 위험성

안전성 위해 요소

@NonThreadSafe
public class UnsafeSequence {
private int value;

public int getNext() {
return value;
}}
  • UnsafeSequence는 경쟁 조건이라는 흔한 위험성을 보여주는 예제
  • 스레드는 서로 같은 메모리 주소 공간을 공유하고 동시에 실행되기 때문에 다른 스레드가 사용 중일지도 모르는 변수를 읽거나 수정할 수 있다. 다른 스레드 간 통신 방식보다 데이터 공유가 쉬워서 편하지만 위험 요소이기도 하다. 순차적인 프로그래밍 모델에 비해 비순차적인 요소가 들어가 혼란스럽고 동작 과정 추론이 어려워질 수 있다.
** 스레드 안전한 일련번호 생성 프로그램
@ThreadSafe
public class Sequence {
@GuardedBy("this") private int value;
public synchronized int getNext() {
return value++;
}}

활동성 위험

  • 스레드 안정성이 “잘못된 일이 생기지 않는다" 를 뜻하는 반면, 활동성은 “원하는 일이 결국 일어난다"는 보완적인 목표에 관한 것
  • 스레드를 사용하면 활동성 관련 문제의 위험성이 더욱 높아진다. 예를 들어 스레드 A에서 스레드 B가 독점하고 있는 자원을 기다리기만 할 것
  • 데드락(deadlock), 소모상태(starvation), 라이브락(livelock)

성능 위험

  • 형편없는 서비스 시간, 반응성, 처리율, 자원 소모, 규모에 따른 확장성 등 넓은 범위의 문제들을 포괄
  • 스레드가 많은 프로그램에서는 컨텍스트 스위칭이 더 빈번하고, 그 때문에 상당한 부담이 생김
    — 실행중인 컨텍스트를 저장하고 다시 읽어들여야 하며, 메모리를 읽고 쓰는 데 있어 지역성이 손실되고, 스레드를 실행하기도 버거운 CPU 시간을 스케줄링 하는 데 소모해야 함
    — 스레드가 데이터를 공유할 때는 동기화 수단도 사용해야 함. 이런 동기화는 컴파일러 최적화를 방해하고, 메모리 캐시를 지우거나 무효화하기도 함
    — 공유 메모리 버스에 동기화 관련 트래픽을 유발
  • 위의 모든 요인은 성능 측면에서 추가적인 손실 유발

GUI 애플리케이션

  • 보통 스레드 하나로 동작, 코드 전반에 걸쳐 사용자 입력을 계속해서 점검하거나 모든 애플리케이션 코드를 ‘메인 이벤트 루프'를 통해 간접적으로 실행해야 함
  • 메인 이벤트 루프에서 직접 호출한 코드가 너무 오랫동안 실행되면 다음 이벤트를 처리할 수 없어 사용자 인터페이스가 멈춘 것 처럼 보이기도 함
  • 메인 이벤트 루프를 이벤트 전달 스레드(EDT)로 대체하여 이벤트가 발생하면 애플리케이션이 정의한 이벤트 핸들러가 이벤트 전달 스레드에서 호출됨.
  • 이벤트 스레드가 짧은 작업만 실행한다면 UI 반응 속도에 별 영향이 없지만 네트워크 통신이 필요한 작업처럼 오래 걸리는 작업을 실행하면 반응 속도가 떨어지게 되기 때문에 시간이 오래 걸리는 작업을 별도 스레드에서 실행하여 이벤트 스레드가 계속 UI 이벤트를 처리할 수 있도록 해야 함
  • 이벤트 스레드 밖에서 GUI를 다뤄야 한다면, GUI를 다루는 코드가 해당 외부 스레드 대신 이벤트 스레드에서 실행되도록 해야 함
    — 사용자가 UI동작을 하면 사용자가 요청한 동작을 수행하기 위해 이벤트 스레드에서 이벤트 핸들러를 호출. 해당 이벤트 핸들러도 여러 스레드에서 접근해 사용하는 애플리케이션 상태에 접근해야 할 수 있지만 그런 경우 해당 상태에 접근하는 다른 스레드와 마찬가지로 이벤트 핸들러 역시 스레드 안전한 방법으로 해당 상태에 접근해야 함
    — 이벤트 스레드에서만 GUI 컴포넌트에 접근할 수 있게 제한하는 방법으로 스레드 안전성을 얻음

--

--

kimji1
kimji1

No responses yet