728x90

1) 여러 개의 스레드를 사용하면 동시에 여러 개의 관련 작업을 실행 할 수 있어서, 애플리케이션의 반응성을 향상시킬수 있기 때문입니다. 또한 여러개의 작업을 동시에 실행하여 더 뛰어난 성능을 달성할 수도 있습니다.

2) 하나의 프로레스에 속한 다수의 스레드는 다음 항목을 공유합니다. 

  • 코드
  • 프로세스의 열린 파일
  • 프로세스의 메타 데이터

3) 운영 체제는 어떤 방식으로 스케줄링한 스레드를 설계해야하나?

운영 체제는 각 스레드의 대한 동적 우선 순위를 유지하여 인터렉티브 스레드를 우선시하고 시스템의 특정 스레드가 기아 상태 가 되는것을 방지합니다. 

 

이 코드는 어떤 작업을 수행하나요?

Thread thread = new Thread(new Runnable() {
  @Override
  public void run() {
     System.out.println("Executing from a new thread");
  }
});

위의 코드는 새 스레드를 생성하는데, 만약 thread.start() 호출을 통해 시작될 경우, 새 스레드의 run() 메서드 안에 있는 코드를 실행하게 됩니다. 

 

public class Main {
    public static void main(String [] args) {
        Thread thread = new TaskThread1();
        thread.start();        
    }
    
    public static class TaskThread1 extends Thread {
        @Override
        public void run(){
            System.out.println("Hello from new thread");
        }
    }
}
코드 스니펫 2:

public class Main {
    public static void main(String [] args) {
        Thread thread = new Thread(new Task2());
        thread.start();
    }
 
    public static class Task2 implements Runnable {
        @Override
        public void run(){
            System.out.println("Hello from new thread");
        }
    }
}

코드 스니펫 1과 코드 스니펫 2의 기능은 동일하며, 모두 맞습니다. 멀티스레드 코드를 구성하는 두 가지의 다른 방식에 해당합니다. 

둘 다 모두 맞습니다. 코드를 구성하는 방식의 차이입니다. 일부 개발자들은 코드와 스레딩 기능을 분리하는 편을 선호하고, 또 Runnable 구현하는 편을 선호하곤 합니다. 다른 시나리오의 경우, 개발자들은 스레딩 기능을 하나의 클래스 안에 포함시키는 편을 선호하며 따라서 스레드를 확장하는 편을 선호합니다. 어느 방식이 맞고, 어느 방식은 틀리다고 할 수 없습니다

728x90
728x90

Runnable 인터페이스로 다른 스레드에서 코드를 실행하는 방법과 진해지는 정도의 수준.

그리고 스레드 클래스의 유용한 기능을 알아보고 IDE를 사용해 스레드를 디버그 하는 법도 해볼게요. 

1. 스레드 생성하는 방법 

Java 에서는 JDK 가 모든 스레드의 관련 속성과 메서드를 스레드 클래스로 압축합니다. 

그래서 새 스레드를 만들려면 새 스레드 객체부터 생성해야하죠!! 스레드 객체 자체는 기본적으로 비어있스빈다. 

그러니 runnable 인터페이스를 구현하는 클래스의 객체를 해당 생성자에 전달해야합니다. 

run 메서드에 어떤 코드를 넣든 운영 체제가 스케줄링하자마자 새 스레드에서 실행될 겁니다. 

자바 8부터는 람다로 줄일수 있죠?!?! 

 

스레드 객체에서 start 메서드를 호출해 스레드를 시작해야합니다. 

 

그럼 JVM이 새 스레드를 생성해 운영 체제에게 전달합니다. 

스레드 클래스에는 유용한 메서드가 몇가지 있습니다. 

예를 들어, start 메서드를 호출 전에 log line을 추가해 현재 실행중인 스레드를 출력하려면 thread.currentThread라는 정적 메서드를 호출해 현재 스레드의 스레드 객체를 알아내야합니다. 스레드 객체를 얻으면 getName메서드나 getId 메서드를 사용할 수 있는데 getName을 사용하겠습니다. 

스레드 클래스에는 sleep 메서드라는 정적 메서드도 있습니다. 아시겠지만, 이 메서드는 현재 스레드를 주어진 밀리초 만큼 멈추게 합니다. 

이건 반복되는 명령문이 아닙니다. sleep 메서드는 운영 체제에게 지시하는것 뿐입니다. 이 시간이 지날때까지는 현제 스레드를 스케줄링을 하지말라는거죠, 이 스레드는 이 시간 동안 cpu를 쓰지 않는겁니다. 

이제 run 메서드에 코드를 추가해 새 스레드에서 실행해 볼게요.

밑에 사진에서 처럼 main이라는 이름의 스레드 객체가 반환됐습니다. 메인 스레드가 실행되었다는거죠 그리고 start 메서드를 호출한 뒤의 새 스레드는 스케줄링 되지 않았어요 시간이 걸리니깐요 운영 체제에 의해 비동기적으로 발생하죠! 

따라서 두 번째 출력문도 main 스레드 입니다. 그 후에 thread-0 이라는 새 스레드 코드가 실행되어 반환이 된거죠.

이 스레드 생성 방법을 통해서 생성법을 알았으니 스레드 클래스를 이용한 응용 기능들을 배워 봅시다. 

우선 JVM 이 새 스레드에 Thread-0 이라는 쓸모 없는 이름이 붙었었죠????

현재 는 괜찮을지도 모르지만 스레드 개수가 많은 어플리케이션을 만들었을때는 스레드의 의미 있는 이름이 추후 디버깅에 큰 도움이 될겁니다.  

스레드 이름을 설정하기 위해 thread.setName 메서드를 호출해 new worker thread 라고 붙이고 어플리케이션을 다시 실행하면 이렇게 새 이름이 콘솔에 출력되게 됩니다.

이전 lecture에서 운영체제가 각 스레드에 동적 우선순위를 적용하는 방법을 배웠습니다. 따라서 스레드 객체를 사용하면 동적 우선순위의 정적 요소를 계획적으로 설정할수 있습니다. setPriority 메서드를 호출해 1부터 10까지의 값을 주거나 사전 정의된 값인 maxPriority, minPriority 아니면 normPriority도 쓸수 있습니다. 전 maxPriority를 사용하겠습니다. 

설정을 유효화하기 위해 새 스레드에 출력 제어문을 더해 우선순위가 설정됐음을 명시합니다. 그러려면 현제 스레드 객체에 getPriroity 메세드를 호출해야합니다.  이제 스레드의 우선순위를 봅시다. 

MAX priroty를 사용했지만 동작에는 차이가 없습니다. 하지만 복잡한 프로그램의 스레드가 보다 많은 응답성을 필요로 하면 아주 중요한 역할을 합니다. 

이제 IDE에서 멀티스레드 애플리케이션을 시각적으로 디버그해 볼게요. 

"before start a new thread" 전에 첫 중단점이 뜹니다. 

디버그 창으로 가서 스레드 탭을 열면 현재 JVM에서 실행 중인 모든 스레드와 스택 추적을 확인할수 있습니다. 

또, 현재 실행중인 메인 스레드와 JVM 이 생성한 새게의 스레드는 당정 제어할 수 없다는것도 확인이 가능합니다. 

이대로  계속해서 다음 중단점으로 보면 493이라는 ID이라는 "new worker thread"가 실행중인것을 알수 있습니다.

메인 스레드와 "new worker thread"가 동시에 실행되고 있는걸 알수 있습니다. 

breakerPointer = 중단점)

중단점을 지정할 때마다 모든 스레드가 동결되고 개별적으로 검사할 수 있다는 뜻입니다. 

새 스레드에 다른 중담점을 추가한 후 계혹한다면 메인스레드는 이미 완료됐고 JVM이 파기했다는걸 알수있습니다. 

 

보통 Java 에서 체크되지 않은 예외는 우리가 직접 캐치해서 특정 방법으로 처리하지 않으면 전체 스레드를 다운시킵니다. 

 

thread.setUncauhtExceptionHandler();

위에 구문을 통해서 처음부터 전체 스데르에 해당되는 예외 핸들러를 지정할 수 있습니다. 

스레드 내에서 발생한 예외가 어디서도 캐치되지 않으면 핸들러가 호출될 겁니다. 그러면 우리는 채키되지 않은 스레드와 

예외를 출력하기만 하면됩니다. 하지만 더 현실적으로 리소스 일부를 정리한다거나 추가 데이터를 로그하면 문제가 발생한 이후에 우리가 해결할 수 있습니다. 이를 구현하기 위해 스레드에 적절한 이름을 붙이고 RuntimeException을 내부 메시지와 throw 합니다. 

이대로 프로그램을 실행하면 Misbehaving thread에서  Intentional Exception이라는 에러 메시지가 발생했다고 뜹니다. 

 

728x90
728x90

스택(stack) 은 메모리영역으로 지역 변수가 저장되고 기능이 실행되는 영역입니다. 그리고 명령어 포인터는 그냥 포인터 일뿐입니다. 

스레드가 실행할 다음 명령어의 주소를 가리키느 역할을 한다. 각각의 스레드가 자체  스택과 명령어 포인트를 가지는지 쉽게 이해하려면 각각의 스레드는 특정 순간에 서로 다른 함수를 이용해 다른 명령을 수행한다는것만 기억하자. 

 

컨텍스 스위치


하나의 스레드 실행을 멈추고 다른 스레드를 스케줄링한 다음 다시 실행하는것이 컨텍스 스위치입니다. 

동시에 많은 스레드를 다룰 때는 효율성이 떨어지기 때문입니다. 이것이 병행성을 위한 대가입니다. 

우리가 생각을 가다듬고 집중력을 회복하는 시간과 똑같습니다. 집이나 직장에서 여러 일을 동시에 하면 생산성이 떨이지게 됩니다. 

주변의 뱅하는 받는다거나 다른 일로 전환하는 순간에 말이죠~! 

운영체제도 마찬가지 입니다.  

 

CPU 에서 실행되는 각 스레드는 CPU 내의 레지스터나 캐시 메모리 내의 커널 리소스 같은 것을 일정 부분 차지하게 됩니다. 

다른 스레드로 전화할 때는 기존의 모든 데이터를 저장하고 또 다른 스레드의 리소스를 CPU와 메모리에 복원해야 합니다. 

 

컨텍스트 스위치에서 알아야한느것은 너무 많은 스레드를 가동하게 되면 'Trashing' 이 발생한다는겁니다. 

이 말은 즉슨, 운영 체제가 우리가 시킨 일을 생산적으로 해내는 대신 스레드 관리 즉, 컨텍스트 스위치에 더 많은 시간을 할애한다는 거죠.

또, 스레드는 프로세스보다 리소스를 더 적게 사용합니다. 프로세스 내의 스레듣르은 공유하는 리소스가 많이 때문입니다. 

 

즉, 같은 프로세스에 속한 두 스레드 간의 컨텍스트 스위치가 각각 다른 프로세스의 두 스레드 간의 컨텍스트 스위치보다 효율적인 겁니다. 

 

컨텍스트 스위칭을 알았으니  운영체제가 어떤 스레드를 실행할지 언제 컨텍스트 스위치를 결정하는지 알아봅시다. 

 

예시) 텍스트 편집기로 과제를 한다고 가정을 합니다. 

좋아하는 음악을 들으면서 텍스트 편집기를 사용한다고 하면, 프로세스는 2개 입니다. 

 

간단한 설명을 위해 음악 플레이어에 스레드 2개가 있다고 합니다. 

하나는 파일에서 음악을 로딩해 스피커로 전송하는거고 

하나는 UI 스레드로 트랙의 상황을 보여주고 재생, 멈춤 버튼의 마우스 클릭에 반응합니다. 

텍스트 편집기도 스레드는 2개 입니다. 

하나는 역시, UI 스레드로 우리가 입력한 걸 보여주며 키보드와 마우스 입력에 반응합니다. 

다른 스레드는 현재 작업을 2초마다 파일에 저장하죠. 

 

 

Arriaval Order Length
1 file Saver
2 Music player
3 text editor UI
4 Music Player UI

 

이렇게 하나의 코어에 스레드 4개가 되었고 이제 코어 하나로 스케줄을 짜야합니다. 

 

작업의 도착순서와 실행시간이 주어진다고 가정한다면 운영체제는 누구를 가정 먼저 실행할까요?

먼저 도착하는 작업이 먼저 실행되는것이라고 가정하면 

위에 1->2->3->4 순서입니다. 단 이 접근법의 문제점은 실행 시간이 긴 스레드가 먼저 도착하면 다른 스레드에 기아 현싱이 발생한다는 겁니다.

특히 UI 스레드에는 큰 문제입니다. 애플리케이션의 응답성을 방해해 최악의 사용자 경험을 야기할 테니까뇽

위에 표를 보듯이 UI 스레드는 보통 짧습니다. UI 스레드는 사용자의 입역을 단순히 화면에 띄우기만 하거든요. 

그럼 짧은 순서대로 스케쥴을 짜면 어떻게 될까요??

아까와 전반대의 문제가 발생합니다. 

 

사용자 관련 이벤트가 항상 시스템에 있는 겁니다. 

가장 짧은 작업을 항상 맨 앞에 배치하게 되면 계산이 들어간 긴 작업들은 영원히 실행이 될수 없는겁니다. 

이 짧은 모의 실험을 통해 우리가 알게 된것이 있습니다. 

운영체제가 스레드에 CPU 시간을 공평하게 할당할 때 어떤 문제점이 생기는지 말이죠. 

그럼 일반적인 운영체제에서는 어떻게 작동하는지 알아봅시다.

 

운영체제는 '에포크'에 맞춰 시간을 적당한 크기로 나눕니다. 

 

그리고 스레드의 타임 슬라이스를 종류별로 에포크에 할당하죠. 

하지만 모든 스레드가 각 에포크에서 실행되거나 완료되지는 않아요. 

스레드에 실간을 할당하는 방법은 운영 체제가 각각의 스레드에 적용하는 동적 우선순위에 달렸습니다. 

정적 우선순위는 개발자가 미리 설정하고 보너스는 운영체제가 각각의 메포크마다 조절합니다. 

이렇게 하면 운영체제는 즉각적인 반은이 필요한 실시간 스레드나 인터랙티브 스레드에게 우선권을 주게 됩니다. 

하지만 동시에 기아 현상을 막기 위해 이전 에포크에서 실행 시간이 부족했거나 완료되 않은 스레드도 놓지지 않습니다. 

첫 번째 멀티 스레드 애플리케이션 작업을 시작하기 전에 마지막으로 확인할 것은 하나의 프로그램에 멀티스레드를 사용하는 시점과 새로 생성환 프로그램을 다른 프로세스에 실행하는 시점입니다. 

 

어떤 방식을 쓸 것인지 마음속으로 결정해야 합니다. 멀티 스레드 접근법과 멀티 프로레스 접근법 중에서 말이죠 !!  

스레드들은 많은 리소스를 공유합니다 그러니 많은 데이터를 공유하는 다양한 작업을 실행하려면 멀티 스레드 애플리케이션 아키텍처가 좋을겁니다 .뿐만 아니라 스레드는 생성과 파기가 훨씬 빠르고 같은 프로세스 안에서 멀티 스레드끼리 전환하는것이 여러 프로세스 사이에서 전환하는것보다 훨씬 빠릅니다. 

하지만 독립된 프로그램을 독립된 프로세스에 실행하는 게 좋을 수도 있습니다. 

가장 중요한 점이 보안과 안정성이라고 하면말이죠

여기서는 각각의 프로세스가 완전히 독립되어 있지만  멀티 스레드 애플리케이션에서는 앱 전체가 스레드 하나에 다운될 수도 있습니다. 

또, 서로 관련이 없는 작업을 실행할 경우 같은 프로세스에 통합하는것도 아무 의미가 없다. 

 

컨텍스트 스위치와 이것이 성능에 미치는 영향을 배웠습니다. 다음 시간에는 운영체제의 일반적인 스레드 스케중링 방법과 각 운영체제가 자체적인 스케줄링 알고리즘으로 움직인다는것도 배웠습니다. 

멀티 스레드 애플리케이션 아키텍처와 멀티 프로레스 애플리케이션 아키텍처가 각각 어느 경우에 유용하게 사용될수 있는지도 알아봤습니다. 

728x90

+ Recent posts