스레드란?
스레드는 프로세스의 실행 단위 이다.
프로세스가 실행 중인 프로그램이라면, 스레드는 프로그램 내에서 작업을 수행하는 작업자라고 표현할 수 있다.
프로세스는 최소 1개 이상의 스레드를 가지며, 스레드는 OS에서 프로세스에게 할당 한 가상 메모리를 공유한다.
구성 요소
- 스레드 ID
- PC(Program Counter)
- 스레드는 CPU에 의해 점유당하다가 스케줄러에 의해 다시 선점 당함
- 때문에 스레드는 어디까지 작업을 수행했는지 알아야 함
- 이를 PC에 저장하며, 스레드는 개별적인 PC를 갖는다.
- 레지스터 집합
- 스택
프로세스의 실행 단위가 하나인 경우에는 단일 스레드 라 하며, 여러 개의 경우 다중 스레드라고 한다.
멀티 스레딩
멀티 스레딩은 하나의 프로세스 내부에서 동시에 여러가지 일을 처리할 수 있도록 한다.
스레드들은 프로세스의 가상 메모리의 코드 영역과 데이터(Heap) 영역을 공유하지만, 스택 영역은 각각 유지한다.
스택 영역
스택 영역은 함수 호출 시 전달되는 인자, 되돌아갈 주소 값 및 함수 내에서 선언하는 변수 등을 저장하기 위해 사용되는 메모리 공간으로, 이를 각각 관리함으로써 스레드 각각이 독립적인 실행 흐름을 가질 수 있다.
동작 방식
- 동시성(Concurrency)
- 여러 개의 스레드가 '번갈아가면서' 실행되는 성질
- 병렬성(Parallelism)
- 한 개 이상의 스레드를 포함하는 각 코어들이 '동시에' 실행되는 성질
동기화
동기화란 데이터 영역을 공유하는 스레드 특성 상 동시에 특정 데이터에 접근하는 충돌이 일어날 수 있다.
이를 해결하기 위한 방법을 동기화(Synchronization)라 하며, 방법에는 뮤텍스와 세마포어가 있다.
스핀락
volatile int lock = 0 //global
void critical() {
while(test_and_set(&lock) == 1);
//TODO Task
lock = 0;
}
int test_and_set(int *lockPtr) {
int oldLock = *lockPtr;
*lockPtr = 1;
return oldLock;
}
- critical 함수에 접근했을 때, lock이 0이면 lock을 1로 바꾸고, 0을 반환하여 while문을 탈출하여 Task를 실행한다.
- 실행 도중 다른 프로세스/스레드에서 critical 함수를 실행했을 경우, lock은 1이므로 while문을 반복하여 돌면서 대기한다.
- 이전 작업이 완료되면 lock이 0이 되면서 대기 중이던 스레드가 while문을 탈출, Task를 실행하게 된다.
TestAndSet은 CPU의 atomic 명령어로 다음과 같은 특징을 같고 있다.
- 실행 중간에 간섭 받거나 중단되지 않는다.
- 같은 메모리 영역에 대해 동시에 실행되지 않는다.
단점 - 대기하는 동안 CPU 낭비
뮤텍스(Mutex)
lock을 획득할 때까지 Queue에서 휴식하는 방식
뮤텍스는 스핀락의 단점을 보완하기 위해 큐에 들어가 대기하는 방법으로 구현되어 있다.
class Mutex{
int value = 1;
int guard = 0;
}
Mutex::lock(){
while(test_and_set(&guard));
if(value == 0) { // 이미 실행 중
//TODO 현재 스레드를 큐에 삽입
guard = 0;
}
else {
value = 0;
guard = 0;
}
}
Mutex::unlock(){
while(test_and_set(&guard));
if(Queue is Not Empty){
wake;
}
else{
value = 1;
}
guard = 0;
}
- lock을 획득해야 작업을 수행할 수 있다.
- Mutex의 value 값을 통해 lock을 얻을 수 있다.
- value가 0이면 이미 실행 중인 스레드가 있다는 뜻으로 큐에 현재 스레드를 추가한다.
- 실행이 끝난 스레드가 unlock을 수행하면 대기 중이던 스레드가 작업을 실행한다.
여기서 value 또한 여러 스레드가 공유하는 자원으로 충돌이 일어날 수 있다.
value의 충돌을 방지하기 위해 있는 것이 바로 guard
때문에 while(test_and_set(&guard)); 에서 guard로 확인하는 절차를 갖는 것을 볼 수 있다.
세마포어(Semaphore)
class Semaphore{
int value = 1; // 1 이외에도 다양한 숫자 가능
int guard = 0;
}
Semaphore::wait(){
while(test_and_set(&guard));
if(value == 0) { // 이미 실행 중
//TODO 현재 스레드를 큐에 삽입
guard = 0;
}
else {
value -= 1;
guard = 0;
}
}
Semaphore::signal(){
while(test_and_set(&guard));
if(Queue is Not Empty){
wake;
}
else{
value += 1;
}
guard = 0;
}
- Signal Mechanism을 가진 하나 이상의 프로세스/스레드가 임계 영역에 접근하도록 하는 장치
이진 세마포어: 0 또는 1의 값만 가질 수 있음
계수 세마포어: 음이 아닌 모든 정수
상호 배제만 필요한 경우 뮤텍스를, 작업 간의 실행 순서 동기화가 필요한 경우 세마포어를 권장한다.
'CS > 운영체제' 카테고리의 다른 글
메모리 (0) | 2025.04.08 |
---|---|
리눅스 파일시스템 (0) | 2025.03.06 |
운영체제와 프로세스 (0) | 2025.03.06 |