문제: 프로세스(Process)와 스레드(Thread)를 정의하고, 여러 개의 스레드가 동일 코드 부분을 병렬로 실행하더라도 모든 스레드가 각각의 고유한 계산 값을 유지할 수 있는 이유를 설명하시오.
답변:
프로세스는 컴퓨터에서 실행 중인 프로그램의 독립적인 인스턴스입니다. 각 프로세스는 고유의 메모리 주소 공간을 부여받으며, 이 공간은 코드(Code), 데이터(Data), 힙(Heap), 스택(Stack) 등의 세그먼트로 나뉘어 관리됩니다. 운영체제는 각 프로세스에 대해 다음과 같은 리소스를 할당하여 독립적으로 실행될 수 있도록 지원합니다.
• 메모리 주소 공간: 프로세스는 독립적인 메모리 공간을 가지며, 다른 프로세스와 메모리를 공유하지 않습니다. 이로 인해 프로세스 간 간섭 없이 독립적인 작업이 가능해집니다.
• 파일 디스크립터(File Descriptor): 프로세스는 자신만의 파일 디스크립터를 사용하여 파일이나 입출력 리소스에 접근합니다.
• CPU 레지스터 상태: 프로세스가 실행될 때 CPU는 프로세스의 레지스터 상태를 로드하고, 프로세스가 일시 중단되면 이 상태를 저장하여 나중에 복원할 수 있습니다.
프로세스 간의 메모리 공유는 불가능하며, 서로 다른 프로세스가 같은 데이터를 참조하려면 IPC(Inter-Process Communication, 프로세스 간 통신) 기술을 사용해야 합니다. 대표적인 IPC 방법으로는 파이프(Pipe), 메시지 큐(Message Queue), 공유 메모리(Shared Memory) 등이 있습니다.
스레드는 프로세스 내부의 작업 실행 단위입니다. 여러 스레드가 하나의 프로세스 내에서 동시에 실행될 수 있으며, 프로세스의 자원을 공유하지만 독립적인 실행 흐름을 가집니다. 스레드는 다음과 같은 특징을 가집니다.
• 공유 메모리 공간: 스레드는 하나의 프로세스 내에서 생성되므로, 코드, 데이터, 힙 영역을 다른 스레드와 공유합니다. 이로 인해 같은 데이터를 여러 스레드가 동시에 접근하거나 수정할 수 있습니다.
• 독립적인 스택: 스레드마다 고유의 스택을 할당받으며, 이 스택에는 함수 호출 시 생성되는 지역 변수와 리턴 주소가 저장됩니다. 스레드 간 메모리 충돌을 방지하는 중요한 역할을 합니다.
• 개별적인 레지스터 상태: 스레드마다 자신만의 레지스터와 CPU 상태를 가집니다. 스레드가 CPU에서 교체될 때, 각 스레드의 레지스터 상태가 저장되고, 다시 실행될 때 복원됩니다.
스레드가 프로세스 내에서 다수 생성될 수 있어, 자원을 효율적으로 사용하면서 병렬 작업을 수행하는 데 유리합니다. 예를 들어 웹 서버에서 각각의 클라이언트 요청을 하나의 스레드가 처리하도록 함으로써 동시성(Concurrency)을 높일 수 있습니다.
스레드가 동일한 코드 부분을 병렬로 실행하더라도, 각 스레드가 고유한 계산 값을 유지할 수 있는 이유는 크게 두 가지로 설명할 수 있습니다:
(1) 스레드마다 고유한 스택(Stack) 공간
스레드마다 고유의 스택 영역이 존재합니다. 스택은 함수 호출 시 생성되는 지역 변수(Local Variable)와 함수의 리턴 주소(Return Address)를 저장하는 영역입니다. 즉, 동일한 코드 내에서 실행하더라도 각 스레드는 자신의 스택을 사용해 독립적인 지역 변수를 유지할 수 있습니다.
예를 들어, 다음과 같은 함수 calculate가 있다고 가정합니다.
void calculate() {
int localVar = 0; // 지역 변수
localVar += 5;
printf("%d\n", localVar);
}
이 함수가 여러 스레드에 의해 호출될 경우, 각 스레드는 localVar 변수를 자신의 스택에 할당받습니다. 따라서 한 스레드가 localVar에 값을 저장해도 다른 스레드의 localVar 값에는 영향을 주지 않습니다. 이러한 방식으로 스택에 할당된 지역 변수는 스레드 간 간섭을 방지하며, 각 스레드가 고유한 값을 유지할 수 있도록 합니다.
(2) 공유 자원에 대한 접근 제어와 동기화(Synchronization)
스레드는 같은 프로세스 내의 메모리 공간을 공유하므로, 힙 영역과 전역 변수(Global Variable) 등 공유 메모리에 동시에 접근할 수 있습니다. 이 경우 상호 배제(Mutual Exclusion), 락(Lock), 세마포어(Semaphore) 와 같은 동기화 기법을 통해 공유 자원을 보호하여 스레드가 고유한 계산 값을 유지할 수 있습니다.
예를 들어, 두 스레드가 동시에 전역 변수 sharedVar를 갱신하려 할 때, 적절한 동기화 메커니즘을 사용하지 않으면 값이 예상치 못한 방식으로 변경될 수 있습니다. 이를 방지하기 위해 뮤텍스(Mutex) 나 세마포어(Semaphore) 를 사용하여 한 번에 하나의 스레드만 sharedVar에 접근하도록 제한합니다. 이를 통해 데이터 일관성을 보장하면서 각 스레드가 고유한 계산 값을 유지할 수 있게 합니다.
요약
• 프로세스는 독립적인 메모리 공간을 가지며, 운영체제에 의해 관리되는 개별 실행 단위입니다.
• 스레드는 하나의 프로세스 내에서 실행되는 여러 실행 흐름으로, 코드, 데이터, 힙을 공유하지만 고유의 스택을 가지며 동기화 기법을 통해 데이터를 관리합니다.
• 고유한 계산 값을 유지하는 이유는 스레드마다 고유한 스택 공간을 가지기 때문이며, 공유 자원에 접근할 때 동기화 기법을 사용해 데이터 간섭을 방지하기 때문입니다.
이러한 구조는 여러 스레드가 동일한 코드를 병렬로 실행할 때도 각 스레드가 독립적으로 동작하며, 프로세스의 효율적인 자원 사용과 높은 동시성을 실현합니다.