Thread
Thread (스레드)
- Light Weight Process라고도 함
- 하나의 프로세스에 여러 개의 스레드를 생성하고 동시에 실행할 수 있으며 프로세스 간에 데이터 접근이 불가능하여 IPC를 이용해야 하는 반면, 각각의 스레드는 프로세스의 모든 데이터에 접근할 수 있다.
- Thread는 각자 실행이 가능한 stack을 가진다.
멀티 프로세싱과 Thread
Multi tasking : 1CPU에 여러 프로세스
Multi Processing: 여러 CPU에 하나의 프로세스. 각 CPU에서 여러 개의 thread를 실행하여 프로세스의 실행 속도를 높인다.
Thread의 장점
- 사용자에 대한 응답성 향상
- ex. 스레드 A: 1부터 100만까지 더함 / 스레드 B: 스레드 A에서 현재까지 더해진 값을 확인
- 자원 공유 효율
- IPC 기법과 같은 자원 공유를 위한 번거로운 작업이 불필요함
- 프로세스 안에 있으므로 프로세스의 모든 데이터에 접근 가능
- 작업이 분리되어 코드가 간결해짐
Thread의 단점
- 프로세스 내 하나의 thread에만 문제가 있어도 프로세스 전체의 thread가 영향을 받는다
- Thread가 지나치게 많으면 context switching이 많이 일어나 성능이 저하된다. Linux에서는 thread를 process처럼 취급하므로 thread를 많이 생성하면 그만큼 스케쥴링해야 하는 thread 수가 늘어나고 context switching이 많이 일어난다.
Thread vs. Process
- 프로세스는 독립적, 스레드는 프로세스의 서브셋
- 프로세스는 각각 독립적인 자원을 가짐, 스레드는 프로세스 자원 공유
- 프로세스는 자신만의 주소영역을 가짐, 스레드는 주소영역 공유
- 프로세스간에는 IPC 기법으로 통신해야 함, 스레드는 필요 없음
Synchronization (동기화)
작업들 사이에 실행 시기를 맞추는 것으로 여러 thread가 동일한 자원에 접근할 시 동기화 이슈가 발생한다.
Mutual Exclusion (상호 배제)
Thread가 프로세스의 모든 데이터에 접근할 수 있으므로 여러 thread가 변경하는 공유 변수에 대해서는 Exclusive Access가 필요하다. 한 thread가 공유 변수를 갱신하는 동안에는 다른 thread가 동시 접근하지 못하도록 막아야 한다.
import threading
g_count = 0
def thread_main():
global g_count
for i in range(10000):
# for i in range(100000):
g_count = g_gount + 1
threads = []
for i in range(50):
th = threading.Thread(target = thread_main) # Thread 객체를 생성
threads.append(th)
for th in threads:
th.start() # Thread 객체 시작
for th in threads:
th.join() # 다른 thread가 끝날 때까지 기다림 -> thread 동기화
print('g_count = ', g_count) # g_count = 500000 / g_count = 3686352
Context Switching 과정에서 g_count 값이 무작위로 변경되면서 제대로 된 값이 출력되지 않았다.
import threading
g_count = 0
def thread_main():
global g_count
lock.acquire()
for i in range(100000):
g_count = g_gount + 1
lock.release()
lock = threading.lock() # Mutual Exclusion
threads = []
for i in range(50):
th = threading.Thread(target = thread_main) # Thread 객체를 생성
threads.append(th)
for th in threads:
th.start() # Thread 객체 시작
for th in threads:
th.join() # 다른 thread가 끝날 때까지 기다림 -> thread 동기화
print('g_count = ', g_count) # g_count = 5000000
lock을 생성하여 lock을 가진 thread만 g_count의 값을 변경할 수 있도록 하고 동기화 이슈를 해결하였다.
Mutex와 Semaphore
Critical Section (임계 구역)에 대한 접근을 막기 위한 LOCKING 메커니즘
Mutex (binary semaphore): 임계 구역에 하나의 스레드만 들어갈 수 있음
Semaphore: 임계 구역에 counter만큼의 스레드가 들어갈 수 있음
Semaphore pseudocode
P(S): wait(S) {
while S <= 0 // busy waiting
;
S--; // 다른 프로세스 접근 제한
}
V(S): signal(S) {
S++; // 다른 프로세스 접근 허용
}
P: 검사 (임계 영역에 들어갈 때) -> S값이 1 이상이면 임계 영역 진입 후 S값 1 차감 (S값이 0이면 대기)
V: 증가 (임계 영역에서 나올 때) -> S값을 1만큼 더하고 임계 영역을 나옴
S: semaphore 값 (초기 값만큼 여러 프로세스가 동시에 임계 영역 접근 가능)
wait(S) {
S->count--;
if (S->count <= 0) {
add this process to S->queue;
block()
}
}
signal(S) {
S->count++;
if (S->count > 0) {
remove a process P from S->queue;
wakeup(P)
}
}
busy waiting을 할 경우 loop 문을 계속 돌게 되는데 이러한 동작은 CPU에 부하를 준다. 그래서 S가 음수일 경우 대기하지 않고 스레드를 대기 큐에 넣었다가 S가 양수가 되는 순간 대기 큐에 있는 스레드를 실행함으로써 이러한 문제를 해결할 수 있다.
Deadlock과 Starvation
Deadlock (교착상태)
두 개 이상의 작업이 서로 상대방의 작업이 끝나기만을 기다려 다음 단계로 진행하지 못하는 상태
교착상태 발생 조건
다음 4가지 조건이 모두 성립될 때 교착상태 발생 가능성이 있음
- 상호배제(Mutual exclusion): 프로세스들이 필요로 하는 자원에 대해 배타적인 통제권을 요구한다.
- 점유대기(Hold and wait): 프로세스가 할당된 자원을 가진 상태에서 다른 자원을 기다린다.
- 비선점(No preemption): 프로세스가 어떤 자원의 사용을 끝낼 때까지 그 자원을 뺏을 수 없다.
- 순환대기(Circular wait): 각 프로세스는 순환적으로 다음 프로세스가 요구하는 자원을 가지고 있다.
교착상태 예방 (deadlock prevention)
4가지 조건 중 하나를 제거하는 방법
- 상호배제 조건의 제거: 임계 영역 제거
- 점유와 대기 조건의 제거: 한번에 모든 필요 자원 점유 및 해제
- 비선점 조건 제거: 선점 가능 기법을 만들어줌
- 순환 대기 조건 제거: 자원 유형에 따라 순서를 매김
교착상태 회피 (deadlock avoidance)
- 교착상태 조건 1, 2, 3은 놔두고, 4번만 제거: 1, 2, 3 제거시, 프로세스 실행 비효율성이 증대
- 교착상태 조건 중, 자원 할당 순서를 정의하지 않음 (순환 대기 조건 제거)
- 상호배제 조건의 제거: 임계 영역 제거
- 점유와 대기 조건의 제거: 한번에 모든 필요 자원 점유 및 해제
- 비선점 조건 제거: 선점 가능 기법을 만들어줌
- 순환 대기 조건 제거: 자원 유형에 따라 순서를 매김
교착상태 발견(deadlock detection)과 회복
- 교착상태 발견(deadlock detection): 교착상태가 발생했는지 점검하여 교착 상태에 있는 프로세스와 자원을 발견하는 것
- 교착상태 회복(deadlock recovery): 교착 상태를 일으킨 프로세스를 종료하거나 교착상태의 프로세스에 할당된 자원을 선점하여 프로세스나 자원을 회복하는 것
기아상태 (starvation)
- 특정 프로세스의 우선순위가 낮아서 원하는 자원을 계속 할당 받지 못하는 상태
- 교착상태와 기아상태
- 교착상태는 여러 프로세스가 동일 자원 점유를 요청할 때 발생
- 기아상태는 여러 프로세스가 부족한 자원을 점유하기 위해 경쟁할 때 특정 프로세스는 영원히 자원 할당이 안되는 경우를 주로 의미함
기아상태 해결 방안
- 우선순위 변경
- 프로세스 우선순위를 수시로 변경해서 각 프로세스가 높은 우선순위를 가질 기회주기
- 오래 기다린 프로세스의 우선순위를 높여주기
- 우선순위가 아닌, 요청 순서대로 처리하는 FIFO 기반 요청큐 사용