[CS] 임계영역, Lock

이정석·2023년 7월 31일
0

CS

목록 보기
6/7

임계영역(Critical Section)

임계영역(Critical Section)은 멀티 프로세스(쓰레드) 환경에서 공유 자원을 변경하는 코드부분을 의미한다. 두 개 이상의 프로세스(쓰레드)가 동시에 임계영역에 진입하게 되면 데이터 불일치와 같은 문제상황이 발생할 수 있다.

발생하는 문제상황은 Race Condition, Deadlock, Starvation이 존재한다.


Lock

임계영역에서 발생하는 문제를 해결하기 위해 임계영역에는 여러 프로세스(쓰레드)가 동시에 접근하지 않도록 보호해야한다. 이를 위해 Lock이라는 개념을 적용한다.

Lock을 구현하는 방법은 언어마다 다르고 행동하는 방법에 따라 여러 방식이 존재한다. 기본적으로는 한 쓰레드가 임계영역에 전급하려할 때 다른 쓰레드에 의해 Lock된 상황이라면 쓰레드의 상태는 Block이나 Wait으로 변한다. 임계영역에 있는 쓰레드는 작업을 다 끝내면 임계구역을 Unlock한다.

Lock을 구현하는 방법은 다음과 같다.

  • 뮤텍스: 한 번에 하나의 쓰레드만 자원을 사용하도록 하는 방법으로 Mutex로 영역을 Lock하고 이후에 Unlock한다.
  • 세마포어: 임계영역에 진입하는 쓰레드의 숫자를 Count함으로 쓰레드의 개수를 조절하는 방식이다.
  • Spin Lock: 쓰레드가 권한(Lock)을 획득할 때까지 반복하는 Lock방식이다.
  • ReadWrite Lock: 공유 자원에 대한 동시 Read는 허용하지만 Write작업이 발생할 때는 쓰레드의 접근을 제한하는 방식이다.

Lock은 상호배제를 구현하기 위한 방법! Lock을 얻은 쓰레드는 임계영역에 자신만 사용할 수 있다는 확신을 가질 수 있다.

1. Block된 쓰레드의 행동

만약 어떤 쓰레드가 임계구역의 Lock을 얻지 못했다면 Lock을 얻기위해서 쓰레드는 어떤 행동을 취해야 할까?

  1. 원하는 자원이 사용가능할 때까지 기다리기
  2. 나중에 다시 시도하기
  3. 자원이 사용가능할 때, 알림이 오도록하기

각각의 방법은 장단점이 존재하는데

  • 1번같은 경우는 Lock을 구현하기 쉽지만 Lock을 얻을 때까지 시간이 오래걸릴수도 있고
  • 2번은 Lock을 얻는데 랜덤성이 존재하며
  • 3번은 알림을 보내는 개체애 대한 부담이 존재한다.

3번을 구현할 때는 디자인 패턴의 옵저버 패턴을 생각해보자!

2. Lock의 잠금순서

여러 자원에 Lock을 할 때 순서가 중요하다. Lock순서가 서로 다른 쓰레드를 동시에 실행하다도면 교착상태가 발생할 수도 있다.

예를 들면, Thread1은 A->B->C 순서로 Lock을 얻고, Thread2는 C->B->A 순서로 Lock을 얻는 상황이 있다 하자.

  1. Thread1가 A에 대한 Lock을 얻고 B에 대한 Lock을 얻는다.
  2. 동시에 Thread2가 C에 대한 Lock을 얻는다.
  3. Thread1Thread2가 가지고 있는 C에 대한 Lock을 얻기 위해 대기한다.
  4. Thread2Thread1이 가지고 있는 B에 대한 Lock을 얻기 위해 대기한다.

위 상황은 교착상태가 발생한 상황이고 만약 Thead1Thread2가 같은 잠금순서를 가지고 있다면 Thread2는 처음에 A에 접근하게 될 것이고 Thread1은 C에 대한 Lock을 얻을 수 있다.

Lock의 잠금순서는 교착상태를 예방하기 위한 방법중 하나이다.


SpinLock

SpinLock은 자원에 대한 Lock이 풀릴 때까지 루프를 돌며 계속 대기하는 Lock방법으로, Lock된 자원에 접근하고자 하는 쓰레드는 Block당하면 공유자원에 대한 접근을 계속 시도한다.

단점

SpinLock의 단점은 Lock을 얻을 수 있는 시간이 보장되지 않을 수 있다는 것이다. 어떤 자원이 한번 Lock되고 오랜시간동안 풀리지 않는 자원이라면 해당 자원에 대한 Lock을 얻기 위해 다른 쓰레드들은 매우 오랜시간동안 대기해야 할 것이다.

1. Backoff

만약, Block되었을 때 일정시간 뒤에 다시 시도하도록 구현하려면 어떻게 해야할까? SpinLock은 Block 당했을 때 바로 자원의 Lock을 얻는것을 시도한다. 그렇다면, Block후 Loop안에 있는 쓰레드는 일정시간 동안 대기상태에 있을 필요가 있다.

일정시간 동안 Wait상태에 있는 것은 Thread.Sleep이나 Thread.Yield를 사용할 수 있다.


ReadWriteLock

ReadWriteLock은 읽기(Read)작업과 쓰기(Write)작업을 따로 제공하는 Lock방법으로 특정한 자원에 대한 읽기 작업은 동시에 이뤄질 수 있지만 쓰기작업은 하나의 쓰레드에서만 제공하는 방법이다.

단점

ReadWriteLock의 단점은 읽기 작업이 쓰기 작업보다 많이 발생하는 경우에 효율적이라는 것이다. 만약, 쓰기 작업이 더 잦다면 쓰기에 대한 Lock을 얻기 위해 다른 쓰레드는 오래 기다리게 될 것이고 이는 곧 연산자원의 낭비가 된다.


참고문헌

  1. 배현직. 게임 서버 프로그래밍 교과서. 길벗, 2020
profile
게임 개발자가 되고 싶은 한 소?년

0개의 댓글