본문 바로가기

CS/운영체제

스레드

스레드란?

스레드는 프로세스의 실행 단위 이다.

프로세스가 실행 중인 프로그램이라면, 스레드는 프로그램 내에서 작업을 수행하는 작업자라고 표현할 수 있다.

프로세스는 최소 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 명령어로 다음과 같은 특징을 같고 있다.

  1. 실행 중간에 간섭 받거나 중단되지 않는다.
  2. 같은 메모리 영역에 대해 동시에 실행되지 않는다.

단점 - 대기하는 동안 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