10. 쓰레드

Thread

Ligth Weight Process라고도 하는 데, 프로세스와 비슷하지만 프로세스보다 가벼운 존재다.

프로세스 : 프로세스끼리는 서로 데이터에 접근 불가
스레드 :
- 하나의 프로세스 안에서 여러 스레드 생성 가능
- 스레드들은 동시에 실행 가능
- 프로세스 안에 있으므로 해당 프로세스 데이터를 모두 접근 가능

쓰레드는 어떤 프로세스 안에서 code, data, heap 영역은 공유하지만,
각자 개인의 스택 영역을 가진다.(프로세스의 스택 영역은 공유하지 않는다.)

멀티 프로세싱과 쓰레드

멀티 태스킹 : 한 cpu가 여러 프로세스를 시분할 하여 돌아가며 작업 수행
멀티 프로세싱 : 여러 cpu가 프로세스들을 병렬로 작업 수행. -> 이 작업은 쓰레드를 여러개로 만들면 가능한 일!

쓰레드 장단점

장점

1. 사용자에 대한 응답성 향상

만약 어떤 작업과 동시에 사용자와 커뮤니케이션 해야 되는 경우, 쓰레드를 사용하지 않으면, 해당 작업이 모두 끝나야 커뮤니케이션이 가능하다.
반면 쓰레드를 사용하면 작업과 커뮤니케이션을 병렬적으로 처리 가능하다.

2. 자원 공유 효율
- IPC 작업 같은 프로세스 간 자원 공유를 위한 작업이 필요 없음

3. 작업이 분리되어 코드가 간결(이건 작성자에 따라 다르다.)

단점

1. 스레드 중 한 스레드만 문제 있어도, 프로세스 전체가 영향 받음
- 프로세스는 서로 독립되어 있어서, 서브 프로세스들 중 하나가 문제 생겨도 다른 서브 프로세스에 영향이 덜 감
- 반면 쓰레드는 해당 프로세스의 자원을 공유하기 때문에, 한 쓰레드의 오류가 큰 문제를 일으킴.

2. 스레드를 많이 생성하면 context switching이 많이 일어나 성능이 저하된다.
- 스레드를 스케쥴링해야 하므로, context switching이 많이 일어나게 된다.

쓰레드 동기화 이슈

동기화: 작업들 사이에 실행 시기를 맞추는 것
여러 스레드가 동일한 자원을 접근해 수정할 경우 동기화 이슈가 발생.

여러 쓰레드가 한 변수를 읽고 쓰기를 한다고 상상해보자.
이때 어떤 쓰레드A가 한 변수를 쓰고, 다른 쓰레드B가 읽는 순으로 진행되어야 하는데, 만약 B가 변수를 읽고 A가 변수를 쓰게 된다면?
우리가 원하는 결과를 얻을 수 없게 된다.

자 g_count라는 변수가 0으로 초기화되어 있고, g_count = g_count +1 이라는 코드를 쓰레드1과 쓰레드2에게 각각 실행시켜
최종적으로 g_count가 2라는 결과값을 얻고자 할 때 예상되는 동기화 이슈를 살펴보자.

g_count = g_count +1 은 세가지 연산이 필요한다. (메모리에서 g_count값을 읽고, 레지스터에서 덧셈을 진행하고, 진행한 값을 다시 g_count 메모리에 저장.)
근데 이때, 쓰레드1에서 덧셈만하고 저장하지 못한 채로 context switching이 일어나, 쓰레드2가 작업을 시작하면 문제가 생긴다!

동기화 이슈 해결 방안

상호 배제(Mutual exclusion, LOKING 매커니즘)

  • 어느 한 스레드가 공유 변수를 갱신하는 동안 다른 스레드가 동시 접근 못하도록 막기!
    • 임계 자원(critical resource) : 접근이 제한되는 자원
    • 임계 영역(critical section) : 접근이 제한되는 영역(코드 상에서)
1
2
3
4
lock.acquire()
for i in range(100000):
g_count += 1
lock.release()

Mutex와 Semaphore

  • Mutex(binary semaphore) : 임계구역에 하나의 스레드만 들어 갈 수 있음
  • Semaphore : 임계구역에 여러 스레드가 들어 갈 수 있음
    • counter를 두어서 동시에 리소스에 접근 할 수 있는 스레드 수를 제어

Semaphore 로직

  • P: 검사(임계영역에 들어갈 때)

    • S >= 1 이면, 임계 영역 진입 && S -1 (S==0이면 대기)
  • V: 증가(임계영역에서 나올 때)

    • S+1 하고 임계 영역 나옴
  • S: 세마포어값(초기 값만큼 여러 프로세스가 동시에 임계 영역 접근 가능)

  • 바쁜 대기(busy waiting) : 프로세스가 대기 중임을 코드로 표현하기 위해 루프를 사용함.
    즉 대기를 위해 cpu리소스가 사용되는 비효율적 상황

  • 대기큐 : S가 음수일 경우, 바쁜 대기 대신 대기큐에 넣자!

psuedo code

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
P(S): wait(S){
while S <= 0 //대기(busy waiting. 기다리는데 cpu 자원 사용...)
;
S--; //다른 프로세스 접근 제한
}

V(S): signal(S) {
S++; //다른 프로세스 접근 허용
}
//대기큐
wait(S) {
S->counter--;
if(S->count <0) {
add this process to S->queue;
block() //대기를 위해, 프로세스를 대기큐에 넣고 block 상태로...
}
}
signal(S){
S->count++;
if (S-> count >= 1){
remove a process P from S->queue;
wakeup(P) //큐에서 프로세스를 꺼내서 깨운다!
}
}

교착상태(Deadlock)와 기아상태(Starvation)

  1. 교착상태

    • 무한 대기 상태 : 복수의 작업이 서로 상대방 작업이 끝나길 기다리느라 다음 단계로 진행되지 않는 상황


스레드1은 lock하여 리소스 1을 사용하고 리소스 2를 사용하기 위해 대기하고,
스레드2는 lock하여 리소스 2를 사용하고 리소스 1을 사용하기 위해 대기한다면?
둘 다 다음 단계로 진행되지 않는다!

교착 상태 발생조건(모두 성립 시 교착상태 발생 가능)

  • 상호배제(Mutual exclusion): 프로세스들이 필요로 하는 자원에 대해 배타적 통제권 요구
  • 점유대기(Hold and wait): 프로세스가 할당된 자원을 가진 상태에서 다른 자원 대기
  • 비선점(No preemption): 프로세스가 어떤 자원의 사용을 끝날 때까지 그 자원을 뺏을 수 없음
  • 순환대기(Circular wait): 각 프로세스가 순환적으로 다음 프로세스가 요구하는 자원을 가지고 있다

이런 원인 중 일부를 해결하면 교착상태를 해결할 수 있다

  1. 기아상태

    • 특정 프로세스의 우선순위가 낮아서 원하는 자원을 계속 할당 받지 못하는 상태
    • 교착상태와 차이?
      • 교착상태 : 여러 프로세스가 동일 자원 점유를 요청할 때 발생
      • 기아상태 : 여러 프로세스가 부족한 자원을 점유하기 위해 경쟁할 때, 특정 프로세스는 할당 안됨

예를 들면 어떤 변수를 10번 접근해서 변경하라는 명령을 100개의 쓰레드가 사용된다면, 일부 쓰레드는 영영 작업을 해보지 못할 것이다!

기아상태 해결

  • 프로세스 우선순위를 수시로 변경
  • 오래 기다린 프로세스의 우선순위 상향
  • 우선순위가 아닌 요청순으로 FIFO 기반 요청큐 사용
Share