프로세스는 실행 중인 프로그램의 인스턴스이다. 각 프로세스는 독립적인 메모리 공간을 갖고 있으며 운영체제에서 별도의 작업 단위로 분리해서 관리된다. 각 프로세스는 별도의 메모리 공간을 갖고 있기 때문에 서로 간섭하지 않는다. 그리고 프로세스가 서로의 메모리에 직접 접근할 수 없다. 프로세스는 이렇듯 서로 격리되어 관리되기 때문에 하나의 프로세스가 충돌해도 다른 프로세스에는 영향을 미치지 않는다. 쉽게 이야기해서 특정 프로세스(프로그램)에 심각한 문제가 발생하면 해당 프로세스만 종료되고 다른 프로세스에 영향을 주지 않는다.
예시 - 인텔리제이, 크롬, 슬랙 등을 동시에 실행했다면 이 각각이 하나의 프로세스다.
스레드는 프로세스 내에서 실행되는 작업의 단위이다. 한 프로세스 내에서 여러 스레드가 존재할 수 있으며 이들은 프로세스가 제공하는 동일한 메모리 공간을 공유한다. 스레드는 프로세스보다 단순하므로 생성 및 관리가 단순하고 가볍다. 즉, 프로세스는 하나 이상의 스레드를 반드시 포함한다.
예시 - 우리가 크롬을 실행했다면 그것은 하나의 프로세스다. 크롬 안에서 여러 탭이 동시에 유투브, 검색, 다운로드를 수행하고 있다면 이건 크롬 프로세스 내부의 여러 스레드가 동작하고 있는 것이다.
프로그램을 실행하면 운영체제는 먼저 디스크에 있는 파일 덩어리인 프로그램을 메모리로 불러오면서 프로세스를 만든다. 그럼 만들어진 프로세스를 어떻게 실행할까?
프로그램이 실행된다는 것은 사실 프로세스 안에 있는 코드가 한 줄씩 실행되는 것이다. 코드는 보통 main
이라는 이름을 가진 스레드가 이 코드를 줄줄이 따라가며 실행한다.
public class Operator {
public static void main(String[] args) {
int sum1 = 1;
int sum2 = sum1 + 1;
System.out.println("sum1 = " + sum1);
System.out.println("sum2 = " + sum2);
}
}
생각해보면 어떤 무언가가 코드를 하나씩 순서대로 실행하기 때문에 프로그램이 작동하고 계산도 하고, 출력도 할 수 있다. 이 코드를 하나씩 실행하면서 내려가는 것의 정체가 무엇일까?
비유를 하자면 마치 실(thread) 같은 것이 코드를 위에서 아래로 하나씩 꿰면서 하나씩 내려가는 것 같다. 이렇듯 프로세스의 코드를 실행하는 흐름을 스레드(thread)라 한다. 스레드는 번역하면 "실", "실을 꿰다"라는 뜻이다.
스레드는 프로세스 내에서 실행되는 작업의 단위이다. 한 프로세스 내에 하나의 스레드가 존재할 수 있고 한 프로세스 내에 여러 스레드가 존재할 수도 있다. 그리고 스레드는 프로세스가 제공하는 동일한 메모리 공간을 공유한다.
하나의 프로세스 안에는 최소 하나의 스레드가 존재한다. 그래야 프로그램이 실행될 수 있다. 정리하면 프로세스는 실행 환경과 자원을 제공하는 컨테이너 역할을 하고 스레드는 CPU를 사용해서 코드를 하나하나 실행한다.
하나의 프로그램도 그 안에서 동시에 여러 작업이 필요하기 때문이다. 예를 들어 유투브는 영상을 보는 동안 댓글도 달 수 있다. 이렇게 하나의 프로세스 안에서 다양한 작업을 병렬로 처리하기 위해 멀티 스레드가 필요하다.
동기화 문제 (Synchronization Issue)
스레드는 메모리를 공유하기 때문에 동시에 동일한 자원에 접근하면 충돌이 발생할 수 있다. 어떤 스레드가 먼저 실행될지 예측할 수 없기 때문에 데이터 일관성이 깨지거나 예외적인 상황이 발생할 수 있다. 이를 방지하려면 synchronized
, Lock
, Atomic
등 별도의 동기화 처리 기법이 필요하다.
디버깅 난이도 증가
스레드의 실행 순서가 매번 달라질 수 있어 문제 발생 시 재현이 어렵고 디버깅이 복잡하다.
예외 전파 위험
하나의 스레드가 치명적인 예외를 던지거나 공유 자원을 잘못 처리하면 전체 프로세스의 안정성에 영향을 미칠 수 있다.
스레드 관리의 부담
스레드 수가 많아질수록 컨텍스트 스위칭 비용이 커지고 자원 관리 및 최적화가 어려워진다. 적절한 스레드 풀 관리가 필요하다.
멀티태스킹이나 멀티스레드 환경에서는 운영체제가 여러 작업을 동시에 처리하는 것처럼 보이게 하기 위해 짧은 시간 간격으로 작업 대상을 전환한다. 이처럼 CPU가 실행 중인 작업의 상태를 저장하고 다른 작업의 상태를 불러와 실행하는 과정을 컨텍스트 스위칭(Context Switching)이라고 한다.
컨텍스트 스위칭이 일어나면 현재 작업(스레드 또는 프로세스)의 상태 정보(레지스터, 프로그램 카운터 등)를 저장하고 다음에 실행할 작업의 상태 정보를 복원하여 이어서 실행한다.
프로세스 간 전환
: 메모리 영역 전체가 바뀌므로 전환 비용이 크다.스레드 간 전환
: 코드, 데이터, 힙 영역을 공유하므로 상대적으로 전환 비용이 작다.즉, 스레드는 컨텍스트 스위칭 비용이 낮아 효율적인 반면 자원을 공유하기 때문에 동기화 문제에 주의해야 한다.
프로세스와 스레드는 개념적 범위부터 다르다. 프로세스는 실행 중인 프로그램 전체를 의미하고 스레드는 그 내부에서 코드를 실행하는 흐름이다. 스레드는 같은 프로세스 안에서 메모리를 공유하기 때문에 자원 측면에서 효율적이고 멀티스레드를 활용하면 빠른 응답성과 병렬 처리를 구현할 수 있다.
하지만 동시에 여러 스레드가 같은 자원에 접근할 수 있다는 점은 동기화 문제를 일으킬 수 있어 프로그래머가 적절한 제어를 해주어야 한다. 멀티스레드는 성능과 효율성 측면에서 강력한 도구이지만 그만큼 설계와 구현에 있어 신중함이 필요한 개념이다. 안정적인 동시성 프로그래밍을 위해서는 스레드의 구조와 제약을 깊이 이해하는 것이 필수다.
참고