Process와 Thread가 무엇일까 🤓


Process

: 실행중인 프로그램

  • 논리적인 연산장치
  • 올라갈 때 code, data, stack, heap 공간 등을 잡기 때문에 무거움
  • 물리적인 매체에 저장되어있는 프로그램이 메모리에 올라가서 프로세서에 의해 실행중인 것

Process의 상태 변화

  • New: 프로세스가 메모리에 올라와 실행 준비를 완료한 상태 (스레드가 생성되고 start()가 호출되지 않은 상태)
  • Ready: 프로세서에서 실행되기 위해 대기하는 상태 (start()가 호출된 상태)
  • Running: Ready 상태에 있는 프로세스 중 하나가 CPU 스케줄러를 통해 선택이 되어 작업을 수행하는 상태
  • Blocked: 특정 자원이나 이벤트를 기다리는 상태
  • Terminate: 프로세스가 종료된 상태

Thread

: 한 프로세스 내에서 나뉘어진 하나 이상의 실행 단위

  • 프로세스의 자원을 이용해서 실제로 작업을 수행하는 것
  • 기존에 올라가있는 프로그램 내에서 별도로 생성이되어 코드와 static 메모리를 공유하기 때문에 resource 차원에서 프로세서보다 가볍다

* main스레드의 경우 Single Thread여서 순차적으로 실행됨

Thread의 상태 변화

  • New: 스레드가 실행 준비를 완료한 상태
  • Runnable: start()가 호출되어 실행될 수 있는 상태
  • Waiting: 다른 스레드가 통지할 때까지 기다리는 상태
  • Timed_Waiting:정해진 시간동안 기다리는 상태
  • Blocked: 사용하고자 하는 객체의 잠금(Lock)이 풀릴 때까지 대기하는 상태
  • Terminate: 실행이 종료된 상태

Thread 상태 변화 메소드

메소드 명설명상태 변화
sleep()실행 중인 스레드를 파라미터에 주어진 시간만큼 잠시 Timed_waiting 하게 만듦.Running → (메소드 호출 후) Timed_waiting → Runnable
join()join() 메소드를 호출한 스레드가 종료될 때까지 기다리도록 스레드를 Wait 상태로 만듦.Running → (메소드 호출 후) Waiting → Runnable
interrupt()일시정지 상태인 스레드를 깨워서 실행가능한 상태(실행대기 상태)로 만듦. (단지 작업 취소를 요청만 하는 것이지 스레드를 강제로 종료시키지는 못함)Waiting → (메소드 호출 후) Runnable
yield()자신에게 주어진 실행시간을 동일하거나 높은 우선순위를 가진 스레드에게 양보함.Running → (메소드 호출 후) Runnable

* Object 클래스에 정의되어 있는 메소드
: synchronized 블럭 내에서만 사용 가능 (호출하는 스레드가 반드시 고유락을 갖고 있어야 함)

  • wait(): 실행 중이던 스레드는 해당 객체의 waiting pool에서 notify( )를 기다림.
  • notify(): 해당 객체의 waiting pool에 있던 스레드 중 임의의 스레드만 Runnable하게 만듦.
  • notifyAll(): waiting pool에 있는 모든 스레드를 Runnable하게 만듦.

실행 후 스레드 상태 변화

  • 쓰레드를 New(생성)하고 start()를 호출하면 바로 Running하는 것이 아니라 실행대기열에 저장되어 차례가 될 때까지 기다려야한다.
  • Runnable 상태였다가 자기 차례가 되면 Running 상태가 된다.
  • 주어진 실행 시간이 다 되거나 yield()를 만나면 다시 Runnable상태가 되고 다음 차례의 스레드가 Running 상태가 된다.
  • 실행 중에 sleep(), wait(), join(), I/O block에 의해 Waiting 상태가 될 수 있다.
  • 일시정지 시간이 다 되거나 notify(), interrupt()가 호출되면 일시정지상태를 벗어나 다시 실행대기열에 저장되어 차례를 기다리게 된다.
  • 실행을 모두 마치거나 stop()이 호출되면 스레드는 소멸된다.

<Process와 Thread 비교 예시>

스터디룸을 빌리는 경우로 생각해봤을 때
프로세스의 경우 항상 사용해야되는 책상과 의자를 사용이 끝나면 모두 치우고, 새로운 예약이 들어올때마다 새로 setting 해야한다.(context switching) 하지만 스레드의 경우 책상과 의자를 (공유되는 자원) 프로세스처럼 매번 다시 setting 할 필요가 없다 (캐싱 적중률 ↑)

멀티 프로세스 (Multi-process) / 멀티 스레드 (Multi-thread)

: 멀티 프로세스는 두 개 이상 다수의 프로세서(CPU)가 두개 이상의 작업을 동시에 처리하는 것이고,
멀티 스레드는 하나의 프로세스에 여러 스레드로 자원을 공유하며 작업을 나누어 수행하는 것이다.
즉, 한 어플리케이션에 대한 작업을 동시에 하기 위한 2가지 처리방식이라고 할 수 있다.

  • 멀티 프로세스는 독립적인 메모리를 가지고 있지만, 멀티 스레드는 자원을 공유하여 스레드끼리 긴밀하게 연결되어 있기 때문에 하나의 스레드에 문제가 생기면 전체 프로세스에 영향을 준다.
  • 멀티 프로세스는 개별 메모리를 사용하기 때문에 context switching 비용이 큰 반면,
    멀티 스레드는 공유된 자원을 사용하여 메모리가 효율적이고 context switching 비용 역시 적다.
  • 멀티 프로세스는 동기화 작업이 필요하지 않지만, 멀티 스레드는 자원을 공유하기 때문에 동기화나 교착상태 등의 문제가 발생할 수 있어 공유자원 관리를 해줘야할 필요 있다.
CPU의 코어는 한 번에 단 하나의 작업만 수행할 수 있는데, 
동시에 실행이 되는 것처럼 보이기 위해서 실행 단위는 시분할로 cpu를 점유하여 context switching을 함

* Context Switching이란?
: CPU에서 여러 프로세스를 돌아가면서 작업을 처리하는 과정

Thread를 생성해보자! 😊

1. Thread 클래스 상속

public class 클래스이름 extends Thread
Thread 스레드이름 = new Thread();
  • 객체를 생성하여 바로 실행이 가능하여 편리하다.
  • 자바에서는 두 개이상의 클래스를 상속할 수 없으므로 다른 클래스를 상속해야 하는 경우에는 사용이 어렵다.

2. Runnable 인터페이스 구현

public class 클래스이름 implements Runnable
Thread 스레드이름 = new Thread(new 클래스이름());
  • 다른 클래스를 상속해야 하는 클래스 구현 시에 사용이 유리하다.
  • 재사용성이 높고 코드의 일관성을 유지할 수 있다.
  • 객체 생성 후 바로 실행이 불가능하여 번거롭다.

동기화 (synchronized)

: 한 스레드가 진행 중인 작업을 다른 스레드가 간섭하지 못하도록 막는 것

멀티스레드 프로세스의 경우 여러 스레드가 같은 프로세스 내의 자원을 공유하여 작업하기 때문에 서로의 작업에 영향을 주게 된다.

그럼 이러한 문제는 어떻게 해결할 수 있을까?
공유 데이터를 사용하는 코드 영역을 임계 영역 (critical section)으로 지정해놓고, 공유 데이터(객체)가 가지고 있는 lock을 획득한 단 하나의 스레드만 이 영역 내의 코드를 수행할 수 있게 한다. 이렇게 되면 해당 스레드가 임계 영역내의 모든 코드를 수행하고 벗어나서 lock을 반납해야만 다른 스레드가 반납된 lock을 획득하여 임계 영역의 코드를 수행할 수 있게 된다.

동기화 구현하는 방식

  1. 메소드 전체를 임계 영역으로 지정
public syncronized void 메소드명() {
	//
}
  • 스레드는 synchronized메소드가 호출된 시점부터 해당 메소가 포함된 객체의 lock을 얻어 작업을 수행하다가 메소드가 종료되면 lock을 반납
  1. 특정한 영역을 임계 영역으로 지정
syncronized (객체의 참조변수) {  /*
	//								여기가 synchronized 블럭
}							   */
  • 참조변수는 lock을 걸고자하는 객체를 참조하는 것이어야 함
  • synchronized 블럭으로 들어가면서부터 스레드는 지정된 객체의 lock을 얻게 되고, 블럭을 벗어나면 lock을 반납

동기화로 인해 생길 수 있는 문제점

한 스레드가 lock을 보유한 채로 메소드를 실행 가능할 때까지 기다리게 된다면, 다른 스레드들은 해당 객체의 lock을 기다리느라 다른 작업들까지 원활하게 진행되지 못할 것이다.

그럼 이러한 문제는 어떻게 해결할 수 있을까?

동기화된 임계영역의 코드를 수행하다가 작업을 더 이상 진행할 상황이 아니면, 일단 wait()을 호출하여 스레드가 lock을 반납하고 기다리게 한다. 그러면 다른 스레드가 lock을 얻어 해당 객체에 대한 작업을 수행할 수 있게 된다. 나중에 작업을 진행할 수 있는 상황이 되면 notify()를 호출해서 작업을 중단했던 스레드가 다시 lock을 얻어 작업을 진행할 수 있게 한다.


여기서 생기는 궁금증 🧐

왜 쓰레드를 실행시킬 때 run()이 아닌 start()를 호출할까?

run() 은 단순히 클래스에 선언된 메소드를 호출하는 것이지만,
start()는 call stack을 생성한 다음 run()을 호출해 생성된 call stack에 run()을 올려 스레드가 독립된 공간에서 작업을 수행한다.

왜 sleep 메소드가 생각처럼 작동이 안될까?

main메소드에서 스레드 두개를 생성한 후 try-catch문에서 thread1에 대해 sleep()을 호출했는데 멈추지 않고 가장 먼저 종료가 되었다. 이유가 무엇일까?

public static void main(String args[]) {
	Thread thread1 = new Thread();
    Thread thread2 = new Thread();
    thread1.start();
    thread2.start();
    try {
    	thread1.sleep(2000);
    } catch(InterruptedException e) {}
}

sleep( )은 항상 현재 실행 중인 스레드에 대해 작동하기 때문에 'thread1.sleep(2000)'과 같이 호출하였어도 실제로는 main메소드가 실행하는 main스레드가 영향을 받기 때문이다. 그래서 sleep()은 'Thread.sleep( )'과 같이 호출해야 한다.

0개의 댓글

관련 채용 정보

Powered by GraphCDN, the GraphQL CDN