본문 바로가기
컴퓨터공학기초 개념/운영체제

28. 스레드(Thread) - 세마포어(Semaphore)

by devraphy 2021. 7. 5.

- 이전 포스팅에서 동기화 이슈에 관해 그 문제점과 해결책에 대해서 알아보았다. 

- 이번 포스팅에서는 동기화 이슈에 관련된 내용을 간략하게 정리하고 전반적인 해결방법에 대해 알아보자. 

 

1. 동기화(Synchronization) 이슈란?

- 여러 스레드가 동일한 자원(데이터)에 접근하여 동시 수정을 하는 경우 발생하는 문제

- 연산 누락, 변수값의 변경 등 계산 결과에 오류를 만든다. 

 

* 알고 가자!!!

- 동기화 문제는 다중 쓰레드, 다중 프로세스 사용 시 발생하는 문제이다.

- 아래의 설명에 나온 해결방안이 꼭 다중 쓰레드를 대상으로만 사용되는 것은 아니다.

- 다중 프로세스 사용 시 동기화 문제를 해결하는 방법이기도 하다. 

 


2. 동기화 이슈의 해결방법

- 비동기(= 순차적) 작업처리

- 즉, 스레드 간의 동일한 자원에 접근하는 순서를 지정하여 동시 접근이 불가능하게 한다. 

- 대표적인 해결방안으로 Mutual Exclusion(상호배제)가 있다. 

 

a) 상호배제 기법(Mutex)이란?

- 동기화 대상이 하나인 경우 사용하는 방식이다. 

- 여러 스레드가 동시에 한 자원에 접근하는 경우, 순서를 지정하여 특정 데이터에 동시접근이 불가능하게 만든다.

- 아래의 예시를 살펴보자. 

lock.acquire()
for i in range(100000):
  g_count += 1
lock.release()

- 스레드 A, B, C, D가 있고 모든 스레드가 위의 코드를 실행한다고 가정하자. 

- 가장 먼저 접근한 스레드부터 데이터에 접근할 수 있고, lock.release()가 실행되기 전까지 다른 스레드는 대기상태가 된다.

- 여기서 2가지 개념이 등장한다. 

    임계자원(Critical Resource): 동시 접근의 대상이 되는 자원(= 데이터)를 의미한다. →  g_count

    임계영역(Critical Section): 임계 자원을 처리하는 코드를 의미한다. → for문 

 


3. Mutex와 Semaphore(세마포어)

* Mutex = Mutual Exclusion

 

- Critical Section(임계영역)에 접근을 막기위한 Locking 메커니즘

    Mutex(Binary Semaphore): 임계영역에 하나의 스레드만 접근 가능, 동기화 대상이 하나인 경우 사용한다. 

    Semaphore: 임계영역에 두개의 스레드가 접근 가능, counter를 사용하여 접근 가능한 스레드 수를 제어,

                               동기화 대상이 하나 이상인 경우 사용한다.  

 

a) semaphore(세마포어) 로직의 이해

- 세마포어는 특정 언어로 설명되기 보다 수도(Pseudo) 코드로 설명되어 있다. 

- 세마포어의 로직 처리가 어떻게 되는지 알아보자. 

    ▶ Pseudo Code: 특정 언어를 몰라도 로직을 이해할 수 있도록 설명한 것 

 

[임계영역에 접근하는 경우]

P(S): wait(S) {
    while S <= 0 // 대기(자리가 없음을 의미 => 무한루프)
        ;
    S--; // 위의 while 조건에 걸리지 않았다면 S-- 를 실행한다.  
         // S의 초기값이 1이고 최초의 스레드 하나가 접근했다면 자리 하나를 없앤다. 
         // 즉, S값에 따른 프로세스의 접근 수를 제한하기 위한 코드다.
}

 

[입계영역에서 나가는 경우]

V(S): signal(S) {
    S++; // S값이 증가했으므로 스레드가 접근 가능한 자리가 생김
         // 즉, 다른 프로세스의 접근허용
}

P(검사): lock.acquire()와 같은 의미, 임계 영역에 들어감.

    - S 값이 1 이상이면 임계 영역에 진입 후, S 값 1 차감 (S값이 0이면 대기한다.)

 

V(증가): lock.release()와 같은 의미, 임계 영역에서 나감.

    - S 값을 1 더하고, 임계 영역을 나온다. 

 

S(세마포어 값): 지정된 초기값 만큼의 프로세스가 동시에 임계 영역에  접근 가능  

 

b) 세마포어 - 바쁜대기(Busy waiting)

P(S): wait(S) {
    while S <= 0 // 대기(자리가 없음을 의미 => 무한루프)
        ;
    S--; // 위의 while 조건에 걸리지 않았다면 S-- 를 실행한다.  
         // S의 초기값이 1이고 최초의 스레드 하나가 접근했다면 자리 하나를 없앤다. 
         // 즉, S값에 따른 프로세스의 접근 수를 제한하기 위한 코드다.
}

- 위의 코드에서 자리가 없어 대기상태인 경우, while문을 통한 무한 루프가 실행된다. 

- 무한 루프를 돌리는 것도 CPU 자원을 할당 받아서 하는 것이다. 

- 결국 CPU 자원의 효율성 면에서는 떨어지는 코드이다. 

- 세마포어에서 스레드가 대기상태를 유지하기위해 무한 루프를 돌리는 것을 바쁜 대기라고 표현한다. 

 


4. 세마포어 - 대기 큐(Queue)

- 세마포어의 바쁜 대기로 인해 CPU 활용에 있어 효율성이 좋지 않다는 것을 알 수 있다. 

- 단점이자 문제가 되는 바쁜 대기를 해결할 수 있는 방법이 대기 큐(Queue)다.

 

 

[임계영역에 접근하는 경우]

wait(S) {
    S->count --; // 변수명이 S->count 
    if(S->count <= 0) { // 자리가 없는 경우
        add this process to S->queue; // 변수명이 S->queue
        block() // 대기 => sleep 상태
    }
}

- S가 음수인 경우, 바쁜 대기 대신에 대기 큐에 순서대로 접근하는 스레드를 넣는다. 

- block() 함수를 통해 일종의 sleep 상태로 만들어 CPU를 사용하지 않도록 한다. 

 

 

[임계영역에서 나가는 경우]

signal(S) {
    S->count ++;
    if(S->count <= 1) { // 자리가 있는 경우
        remove a process P from S->queue;
        wakeup(P) // 잠들어 있는 다음 차례의 프로세스를 깨움
     }
}

- wakeup() 함수를 통해  대기 큐에 있는 프로세스를 재실행 한다. 

 


5. Linux - POSIX 세마포어

- 리눅스 운영체제에는 내부적으로 세마포어가 정의되어 있다. 

 

▶ sem_open(): 세마포어 생성

▶ sem_wait(): 임계영역 접근 전, 세마포어를 잠근다. 세마포어가 잠겨있다면, 세마포어가 풀릴 때까지 대기한다.

▶ sem_post(): 공유자원에 대한 접근이 끝났을 때, 세마포어의 잠금을 해제한다. 

댓글