본문 바로가기
CS

프로세스와 스레드

by 감자b 2024. 12. 26.

프로세스의 개념

컴퓨터에서 실행 중인 프로그램을 뜻하는 것으로 운영체제(OS)가 프로그램을 실행시키면, 프로세스라는 단위로 관리된다.

이 때 각 프로세스는 고유한 메모리 공간을 할당받는데 해당 공간에는 프로그램 코드(명령어들), 데이터(변수, 스택, 힙 등)가 있다.

또한 독립적으로 동작하기 때문에 다른 프로세스와 메모리 공간을 공유하지 않는다.

 

  • CPU: 프로그램의 명령어를 실제로 처리하는 장치
  • 메인 메모리(RAM): 프로세스가 CPU에서 실행되기 위해 대기하는 곳으로 프로세스의 실행 코드와 데이터가 여기에 존재한다.

 


단일 프로세스

초창기 컴퓨터의 방식으로 CPU는 한 번에 하나의 작업만 할 수 있는데 이는 실행 중인 프로그램이 있다면 다른 프로그램은 실행할 수 없다. 만약 프로그램이 I/O 작업을 하여 대기 상태(리소스를 기다릴 때 CPU 사용을 반납함)가 된다면 프로세스는 대기 상태가 되는데 이 때 다른 프로그램을 실행할 수 없어 CPU 이용률이 떨어진다.


멀티 프로그래밍

CPU에는 하나의 프로그램만 돌고, 나머지는 대기열에서 관리하도록 한 후 우선 순위에 따라 여러 개의 프로그램을 교대로 실행하여 CPU 이용률을 높이는 방식을 의미한다.

즉 실행 중인 프로그램이 I/O 작업을 하여 대기 상태가 되면 다른 프로그램을 실행한다.

 

스레드의 작업 종류

  • CPU 바운드 작업
    • 계산, 데이터 처리, 알고리즘 실행과 같이 CPU 연산이 많이 요구되는 작업을 의미
  • I/O 바운드 작업
    • 디스크, 네트워크, 파일 시스템과 같은 입출력 작업을 많이 요구하는 작업으로 I/O 작업은 작업이 완료될 때 까지 기다리는 대기 시간이 발생하여 CPU가 대기 상태에 있어 CPU를 사용하지 않는 작업을 말한다.

멀티태스킹

멀티프로그래밍의 경우 한 프로그램의 작업 시간이 길다면 다른 프로그램들은 대기열에서 빠져나오지 못하는 기아 상태가 발생할 수 있다. 따라서 이러한 문제를 해결하기 위해 프로그램이 스스로 기다리는 상태가 되지 않더라도 OS가 강제로 컨텍스트 스위칭을 일으켜 대기열에 있는 프로그램이 동시에 실행되는 듯한 느낌을 주는 방식을 의미한다.

→ Time Sharing 방식을 이용해 실행 시간을 분할해서 프로그램A는 0.01초 수행한 후 멈추고, 프로그램B를 0.01초 수행하고 다시 돌아가는 방식으로 반복해서 동작한다.

→ 실제로는 동시에 실행되지 않지만 동시에 실행되는 것 처럼 보이는 동시성이라는 특징이 있다.

 

위 그림과 같이 프로그램A, B가 있을 때 하나의 코어가 프로그램A를 0.01초 수행 후 프로그램B를 수행하는 방식을 반복해서 여러 프로그램이 하나의 코어에 동시에 실행되는 듯한 느낌을 주는 방식이다.

(우측 사진에서 CPU 코어가 프로그램A,B 코드를 한 번에 모두 수행하는 것 처럼 그림을 그렸지만 실제로는 하나씩 수행된다. 프로세스를 시분할로 쪼개어 프로세스가 번갈아가면서 실행되는 것임)

컨텍스트 → 현재 작업 중인 문맥이라는 의미로 CPU가 현재 실행 중인 기계어 코드의 상태들을 뜻한다.
컨텍스트 스위칭 → 기존의 컨텍스트를 메모리에 저장, 메모리에 있던 컨텍스트를 CPU로 불러오는 과정.

멀티프로세싱

말 그대로 컴퓨터 시스템에서 여러 개의 프로세서를 사용해서 여러 작업을 동시에 처리하는 것을 의미한다.

 

아래의 그림은 멀티 프로세싱의 예를 보여주고 있다. 코어 2개가 여러 작업을 동시에 처리하고 있으며 코어1이 프로그램A의 코드1을 실행, 코어2 B의 코드1을 실행한다. 프로그램A, C의 코드를 수행하는 식으로 반복해서 처리한다. 따라서 아래의 그림은 여러 코어가 동작하므로 멀티 프로세싱이며 각 코어가 여러 프로그램을 시분할하여 처리하고 있으므로 멀티 태스킹이다.

 

멀티태스킹소프트웨어적 관점

멀티프로세싱 → 하드웨어적 관점


프로세스

운영체제 내부에서 실행 중인 프로그램을 의미한다.

  • 메모리 구성
    • 코드 : 실행 할 프로그램의 코드가 저장되는 공간
    • 데이터 : 전역 변수, 정적 변수가 저장되는 공간
    • 힙 : 동적으로 할당되는 메모리 영역
    • 스택 : 메서드 호출 시에 생성되는 프레임 내의 지역 변수, 반환 주소가 저장되는 공간

 

각 프로세스는 독립적인 메모리 공간을 가진다. 그리고 운영체제에서 별도의 작업 단위로 분리, 관리된다. 이렇게 독립적인 메모리를 가지고 있으므로 프로세스 간 메모리에 직접 접근이 불가능하며, 다른 하나의 프로세스에 문제가 발생해도 다른 프로세스에 영향을 미치지 않는다.

  • 프로세스의 상태
    • Started : 프로세스가 처음 생성되었을 때 초기 상태
    • Ready : 프로세스가 실행되기 위해 OS로부터 CPU의 할당을 기다리는 상태
      • Ready → Running (dispatch) : 준비 상태의 여러 프로세스 중 하나가 스케줄러에 의해 실행될 때
    • Running : CPU를 할당받아 기계어 코드를 실행하는 상태
      • Running → Ready (interrupt): CPU time slice를 다 썼을 때(Timeout), 예기치 못한 이벤트가 발생했을 때.
      • Running → Waiting : Blocking 함수를 호출했을 때(I/O 작업)
    • Waiting : I/O 작업 등으로 리소스를 기다리느라 CPU를 반납하고 대기하는 상태
    • Terminated : 작업이 종료되어 메모리 등의 자원을 완전히 반환하는 상태

 


스레드

프로세스 내에서 실행되는 작업의 단위를 뜻하며 하나의 프로세스에는 무조건 하나 이상의 스레드를 가진다. 한 프로세스 내에 속하므로 프로세스와 메모리 공간(코드, 데이터, 힙)을 공유하며 스레드는 독립적인 스택 공간을 지닌다.

스레드는 프로세스에 비해 가볍고 생성과 관리가 단순하며 같은 프로세스의 스레드 간에는 컨텍스트 스위칭 작업이 가볍다.

현재는 CPU에서 실행되는 단위다.

프로세스 → 실행 환경과 자원을 제공하는 컨테이너
스레드 → 실제로 CPU 자원을 사용해서 코드를 실행하는 것

 

아래는 위에서 멀티태스킹을 설명했을 때의 그림이다.

실행 중인 프로그램A, B, C는 프로세스가 되며 프로세스 내부의 코드1, 코드2, 코드3은 스레드 단위로 묶이게 된다.

각 프로그램들이 싱글 스레드라고 가정했을 때 코어1은 프로세스A의 스레드에 있는 코드1을 실행하고, 코어2는 프로세스B의 스레드1에 있는 코드1을 실행한다.

그리고 각 스레드의 실행을 잠시 멈춘 후 다른 프로세스의 스레드 내부의 코드를 실행하는 과정을 반복하여 멀티태스킹을 한다고 보면 된다.

 

좀 더 자세히 얘기하면 이러한 과정은 운영체제 내부의 스케줄링 큐를 통해 진행된다.

실행할 스레드들을 스케줄링 큐에 집어넣고 CPU는 큐 내부의 스레드들을 가져와서 작업을 진행한다. 작업이 처리되지 않는 스레드들은 큐에서 대기하고 있으며 코어들은 큐에서 작업을 꺼내고 집어넣고를 반복하여 작업을 진행한다.

이렇게 어떤 프로그램이 얼마나 실행될 지 결정하여 CPU 시간을 여러 작업에 배분하는 것을 스케줄링이라고 한다.

 

스레드는 크게 두 종류로 나뉜다.

  • 사용자 스레드
    • 생성/소멸, 스케줄링, 컨텍스트 스위칭을 유저 레벨 라이브러리에서 구현 스레드를 의미한다.
    • 즉 커널은 해당 스레드들의 정보를 가지지 않고 프로세스 단위로 스케줄링을 한다.
    • 커널이 개입(시스템 콜)하지 않으므로 빠르며, OS에 독립적이고, 프로세스가 스레드 스케줄링을 제어할 수 있다는 장점이 있지만 하나의 스레드가 Block 상태가 되면 프로세스 전체가 Block 되고 커널에서는 하나의 스레드만 존재하여 멀티프로세싱의 병렬적으로 실행된다는 장점 또한 사라진다.

 

  • 커널 스레드
    • 생성/소멸, 스케줄링, 컨텍스트 스위칭을 커널이 관리한다. 즉 스레드 별로 CPU를 할당하여 멀티프로세싱에서 병렬적으로 처리되고 프로세스 내의 스레드가 Blocking 되어도 다른 스레드 계속 동작한다. 하지만 스레드 간 컨텍스트 스위칭이나 생성/소멸에 커널이 관여하여 느리며 스레드 구현이 OS에 의존적이다.

 

  • 스레드의 상태
    • New : 스레드가 생성되었지만 아직 호출되지 않은 상태 (자바에서 start() 호출 전)
    • Runnable : 스레드가 실행되기 위해 대기 중인 상태로 언제든 CPU를 할당받아 실행 준비가 되어있는 상태 (start() 호출, 자바에서는 스케줄러의 대기열에 존재하는 스레드나 실제 실행되는 스레드나 모두 Runnable 상태)
    • Blocked : 스레드가 I/O와 같은 이벤트에 의해 대기하는 상태
    • Terminated : 스레드 실행이 모두 끝나 종료된 상태. 더 이상 실행되지 않고 메모리에서 제거됨

자바에서는 Blocked 상태가 다음과 같이 나뉜다.

Blocked (차단) : 스레드가 동기화 락을 기다리는 상태 (synchronized 블록에 진입해야 하는데 다른 스레드가 락을 가지고 있는 경우)

Waiting (대기) : 스레드가 무기한으로 다른 스레드의 작업을 기다리는 상태 (wait(), join() 호출되었을 때로 notify(), notifyAll() 호출 전 또는 join() 완료까지 기다림)

Timed Waiting (시간 제한 대기) : 스레드가 일정 시간 동안만 다른 스레드의 작업을 기다리는 상태 (Waiting 상태에서 시간만 추가된 것으로 sleep(mills), wait(), join()에 전달한 시간만큼 대기)

 


자바에서의 프로세스와 스레드

자바 애플리케이션 역시 프로그램이다.

즉 자바 프로그램을 컴파일하여 실행하면 JVM은 하나의 프로세스가 되고, JVM 내부에 여러 개의 스레드들이 실행될 수 있다.

그리고 자바에서 스레드는 사용자(유저) 스레드에 해당한다.

멀티 프로세싱과 멀티 태스킹을 설명할 때 큐 내부에 스레드들을 넣고 이를 가져와서 CPU가 처리한다고 설명했었는데 사용자 스레드라면 커널 공간에 프로세스 별로 스레드가 하나씩 생성되어 병렬적으로 실행하는 것이 불가능하다.

 

하지만 자바에서 스레드는 병렬적으로 처리가 가능한데 어떻게 된 것일까?

자바는 사용자 스레드와 커널 스레드 간의 관계를 원 투 원 모델을 통해 구현하였다.

이는 하나의 사용자 스레드가 하나의 커널 스레드에 매핑되는 모델을 뜻하는데 이로 인해 JVM에서 생성되는 자바 스레드는 사용자 스레드지만 실제 실행은 커널 스레드로 매핑되어 실행이 되는 상호보완적 관계이다.

따라서 자바에서 스레드는 커널 스레드의 도움을 받아 멀티스레딩을 구현하며, 커널 스레드는 자바의 스레드 실행 순서를 관리한다.

 

자바에서의 스레드 생성 방법

  • Thread 클래스 상속
public class MyThread extends Thread {
    @Override
    public void run() {
		    System.out.println(Thread.currentThread().getName());
        System.out.println("MyThread.run");
    }
}

 

  • Runnable 인터페이스 구현
    • Thread 상속에 비해 Runnable의 경우 인터페이스이므로 다중 상속이 가능하고 스레드와 실행할 작업의 구분이 가능하여 가독성이 뛰어나고 유연하다는 장점이 있다.
public class MyRunnable implements Runnable {
    @Override
    public void run() {
		    System.out.println(Thread.currentThread().getName());
        System.out.println("MyRunnable.run");
    }
}
public class ThreadMain {
    public static void main(String[] args) {
        System.out.println(Thread.currentThread().getName() + " 시작");

        MyThread myThread = new MyThread(); // Thread 클래스 상속 스레드 실행
        myThread.start();

        MyRunnable myRunnable = new MyRunnable(); // Runnable 인터페이스 구현 스레드 실행
        Thread thread = new Thread(myRunnable);
        thread.start();

        System.out.println(Thread.currentThread().getName() + " 종료");
    }
}

 

여기서 분명 run() 메서드를 오버라이딩 했는데 스레드를 생성하고 실행하는 부분을 보면 start() 메서드를 사용했다. 이러한 이유는 start() 메서드를 사용해야 새로운 스레드가 생성되고 생성된 스레드의 스택 공간을 할당하게 된다. 만약 run()을 호출한다면 메인 스레드의 스택에 run() 메서드 스택 프레임이 올라가게 된다.

public class ThreadMain {
    public static void main(String[] args) {
        System.out.println(Thread.currentThread().getName() + " 시작");

        MyThread myThread = new MyThread();
        myThread.run();

        MyRunnable myRunnable = new MyRunnable();
        Thread thread = new Thread(myRunnable);
        thread.run();

        System.out.println(Thread.currentThread().getName() + " 종료");
    }
}

출력 결과

main 시작

main

MyThread.run

main

MyRunnable.run

main 종료

 

  • 자바에서 스레드의 종류
    • 사용자(유저) 스레드
      • 프로그램의 주 작업을 수행하는 스레드로 JVM에 존재하는 모든 사용자 스레드가 종료되면 JVM도 종료 스레드를 생성하면 기본은 사용자 스레드로 생성된다.
    • 데몬 스레드
      • 백그라운드에서 보조 작업을 수행하는 스레드로 해당 스레드들의 작업 완료 여부와 관계없이 JVM이 종료될 수 있다. thread.setDaemon(true) 옵션을 주어야 해당 스레드는 데몬 스레드로 동작한다.

멀티프로세스와 멀티스레딩

예를 들어 하나의 프로세스가 2개의 스레드를 가졌고, 2개의 코어를 가진 CPU에서 실행된다고 가정하면 이 스레드들은 코어에 각각 실행된다. 이렇게 병렬적으로 실행되는 구조를 멀티스레딩이라고 하며, 이는 하나의 프로세스가 동시에 여러 작업을 실행하는데 목적이다. 하지만 하나의 스레드에서 문제가 발생하면 다른 스레드들도 영향을 받아 프로그램에 지장이 생길 수 있고, 공유 자원에 여러 스레드가 동시에 접근할 수 있기에 동기화가 필수적임.

 

멀티프로세스는 부모 프로세스에서 시스템 콜을 통해 자식 프로세스 생성하여 다중 프로세스를 형성하는 구조를 뜻한다. 이렇게 만들어진 프로세스들은 모두 독립적이므로 영향을 미치지 않는다.

하지만 멀티 태스킹을 위한 컨텍스트 스위칭 과정에서 프로세스는 스레드보다 무거우므로 오버헤드가 비교적 발생한다.

→ 프로세스 컨텍스트 스위칭은 프로세스 자체가 교체되므로 CPU 캐시 메모리를 초기화하기 때문에 메인 메모리에서 데이터를 가져와야함

→ 프로세스는 독립적이므로 프로세스 간 통신에 IPC를 사용해야함. 복잡하고 어려움.

→ 컨텍스트는 PCB로 관리

 

위에서 설명한 멀티태스킹은 프로세스 뿐만 아니라 여러 스레드도 역시 짧게 시분할하여 실행하는 것이라 생각하면 된다.

  멀티스레딩 멀티프로세스
정의 하나의 프로세스 내에서 여러 스레드를 실행 여러 개의 독립적인 프로세스 실행
자원 공유 여부 스레드 생성 시 스택만 독립적이고 나머지는 공유 프로세스마다 독립적
오버헤드 비교적 적음 비교적 많음
안정성 한 스레드가 프로세스 전체에 영향을 미침 독립적이므로 안정적
컨텍스트 단위 TCB (가벼움 → 스레드들 간 자원 공유하므로 스택 및 register 포인터 정보만 저장함) PCB (무거움)

 

멀티프로세싱이란 두 개 이상의 프로세서나 코어를 활용하여 여러 작업을 동시에 처리하는 것을 뜻한다.

(멀티프로세스랑 헷갈려하지 말 것, 멀티프로세서 → 한 CPU에 여러 코어가 있는 것)

'CS' 카테고리의 다른 글

HTTP의 이해  (0) 2024.12.27
CORS  (0) 2024.12.26
웹 서버, WAS, 리버스 프록시  (0) 2024.12.26
쿠키, 세션, 토큰  (0) 2024.12.26
SOLID 원칙  (0) 2024.12.26