2024. 5. 2. 00:45ㆍCS/운영체제
스레드란 프로세스(관리의 단위)를 구성하는 실행의 흐름 단위(연산의 단위)이다.
프로세스는 최소 1개의 스레드가 존재하며 여러 개의 스레드를 가질 수도 있다.
전통적인 관점에서 하나의 프로세스는 한 번에 하나의 일만 처리했다. 이렇게 실행되는 프로세스를 단일 스레드 프로세스라고 한다.
하지만 스레드란 개념이 도입되면서 하나의 프로세스가 한 번에 여러 일을 동시에 처리할 수 있게 되었다.
즉, 프로세스를 구성하는 여러 명령어를 동시에 실행할 수 있게 되었다.
김밥천국 같은 식당에서는 양식인 돈까스도 팔고, 한식인 떡볶이도 팔 수 있는 것처럼 말이다.
조금 더 직관적인 예시를 들어보자.
한 가구를 프로세스라고 생각하고 세대원들을 스레드라고 생각하자.
집이라는 공간은 운영체제가 프로세스에게 할당한 가상 메모리(Virtual Memory)이다.
그림에서 알 수 있듯이 세대원들의 영역은 집 즉, 가상 메모리에 한정되는 것을 알 수 있다.
또한 세대원 각각의 고유 영역인 방이 존재한다. 이것을 Thread Local Storage라고 부르며 stack이 여기에 해당한다.
하지만 거실이나 부엌, 화장실 같이 공유하며 사용할 수 있는 영역도 존재한다. heap 영역이 여기에 해당한다.
위의 예시로 정리하면 프로세스의 스레드들은 실행에 필요한 최소한의 정보(프로그램 카운터를 포함한 레지스터, 스택)만을 유지한 채 프로세스 자원을 공유하며 실행된다. 이것이 스레드의 핵심이다.
멀티 프로세스와 멀티 스레드
직관적으로 여러 프로세스를 동시에 실행하는 것을 멀티 프로세스라고 하고, 여러 스레드로 프로세스를 동시에 실행하는 것을 멀티 스레드라고 한다.
그렇다면 동일한 작업을 수행하는 단일 프로세스 여러 개를 실행하는 것과 하나의 프로세스를 여러 스레드로 실행하는 것은 무슨 차이가 있을까?
화면에 hello, os를 출력하는 프로그램이 있다.
이 프로그램을 3번 fork하여 실행하는 것과 hello, os를 출력하는 스레드를 3개 만들어 실행하는 것에는 무슨 차이가 있을까?
중요한 것은 프로세스끼리는 기본적으로 자원을 공유하지 않지만, 스레드끼리는 같은 프로세스 내의 자원을 공유한다는 것이다.
(프로세스끼리 자원 공유가 불가능한 것은 아니다. IPC 즉, 프로세스 간 통신이 공유 메모리, 소켓, 파이프 등을 통해 가능하다. 하지만 스레드에 비해서 까다롭다.)
프로세스를 fork하여 같은 작업을 하는 동일한 프로세스를 동시에 실행하면 코드영역, 데이터영역, 힙영역 등 모든 자원이 복제되어 메모리에 적재된다. 즉, PID와 프로세스가 저장된 메모리의 주소를 제외한 나머지 자원이 똑같이 메모리에 저장되는 것이다.
이것은 메모리에 동일한 내용이 중복되어 존재하게 되므로 메모리 낭비이다.
하지만 같은 프로세스 내의 모든 스레드는 동일한 주소 공간의 코드, 데이터, 힙 영역을 공유하고, 열린 파일과 같은 프로세스 자원을 공유한다. 따라서 메모리 효율성을 높일 수 있고 서로 협력과 통신에 유리하다.
그렇다고 장점만 존재하는 것은 아니다. 모든 스레드는 프로세스의 자원을 공유하고, 하나의 스레드에 문제가 생기면 다른 스레드에 영향을 받을 수도 있다.
코드로 스레드 구현하기
이 코드는 프로세스의 ID를 출력하는 프로세스에 새로운 스레드를 만드는 코드이다.
#include <stdio.h>
#include <unistd.h>
#include <pthread.h>
void * foo() {
printf("process id is %d\n", getpid());
return NULL;
}
int main()
{
pthread_t thread1; // thread1 이라는 이름으로 pthread_t형 변수 선언
pthread_create(&thread1, NULL, foo, NULL); // thread1은 foo를 실행하도록 thread 생성
pthread_join(thread1, NULL); // thread1 실행
return 0;
}
결과는 다음과 같다.
process id is 71253
이번에는 동일한 작업을 수행하는 스레드를 생성해보자.
#include <stdio.h>
#include <unistd.h>
#include <pthread.h>
void * foo(){
long thread_id = (long int) pthread_self();
printf("process id is %d\n", getpid());
printf("this is thread %ld\n", thread_id);
return NULL;
}
int main(){
pthread_t thread1;
pthread_t thread2;
pthread_t thread3;
pthread_create(&thread1, NULL, foo, NULL);
pthread_create(&thread2, NULL, foo, NULL);
pthread_create(&thread3, NULL, foo, NULL);
pthread_join(thread1, NULL);
pthread_join(thread2, NULL);
pthread_join(thread3, NULL);
return 0;
}
실행결과
process id is 60932
this is thread 6095646720
process id is 60932
this is thread 6096220160
process id is 60932
this is thread 6096793600
여기서 주목해야할 점은 프로세스의 ID가 같다는 것이다.
즉, 세 개의 스레드는 모두 다른 스레드이지만, 이들은 동일한 프로세스를 공유하고 있다는 것이다.
물론 아래와 같이 각기 다른 스레드가 다른 작업을 수행하도록 만들수도 있다.
#include <stdio.h>
#include <unistd.h>
#include <pthread.h>
void * foo(){
printf("foo executed\n");
return NULL;
}
void * bar(){
printf("bar executed\n");
return NULL;
}
int main(){
pthread_t thread1;
pthread_t thread2;
pthread_create(&thread1, NULL, foo, NULL);
pthread_create(&thread2, NULL, bar, NULL);
pthread_join(thread1, NULL);
pthread_join(thread2, NULL);
return 0;
}
'CS > 운영체제' 카테고리의 다른 글
CPU 스케줄링 알고리즘 (2) | 2024.05.02 |
---|---|
CPU 스케줄링 개요 (0) | 2024.05.02 |
프로세스 상태와 계층 구조 (0) | 2024.05.01 |
프로세스 개요 (0) | 2024.04.27 |
✨운영체제의 큰 그림 (0) | 2024.04.24 |