Post

경쟁상태(Race condition) 1

경쟁 상태

여러 개의 스레드가 동일한 자원에 접근하여 작업할 때 작업 순서가 보장되지 않아 진행할때마다 결과가 달라지는 경우를 말한다. 여기서 동일한 자원은 대부분 공유 메모리이다.

문제 제기

생산자 로직이 있고 소비자 로직이 있다고 가정해보자. 그리고 이 두 개의 로직은 한 개의 프로세스 내에서 각각 스레드로 돌아가며 count 변수 값을 공유한다. 이 경우 생산자와 소비자 로직이 하는일은 다음과 같다.

생산자

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
while (true) {
    // 항목을 생산하고 nextProduced에 넣습니다.
    
    // 버퍼가 가득 찼는지 확인한다.
    while (count == BUFFER_SIZE)
        ; // 대기

    // 버퍼에 nextProduced를 넣는다.
    buffer[in] = nextProduced;
    
    // in을 다음 위치로 이동한다. (버퍼의 크기를 넘어가면 다시 0으로 되돌린다.)
    in = (in + 1) % BUFFER_SIZE;
    
    // 카운터를 증가
    count++;
}

소비자

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
while (true) {
    // 버퍼가 비어 있는지 확인
    while (count == 0)
        ; // 대기

    // 버퍼에서 다음으로 소비할 항목을 가져온다.
    nextConsumed = buffer[out];
    
    // out을 다음 위치로 이동한다. (버퍼의 크기를 넘어가면 다시 0으로 되돌린다.)
    out = (out + 1) % BUFFER_SIZE;
    
    // 카운터를 감소
    count--;
    
    // nextConsumed에서 항목을 소비한다.
}

문제 발생 절차

생산자 로직의 count를 ++하는 로직을 어셈블리어로 바꾸게되면 아래와 같다.

1
2
3
4
5
6
// 메모리에서 count 값을 레지스터로 옮긴다
register1 = count
// 레지스터에서 산술 연산을 한다.
register1 = register1 + 1 
// 메모리로 레지스터 값을 옮긴다
count = register1

소비자 로직의 count를 –하는 로직을 어셈블리어로 바꾸게되면 아래와 같다.

1
2
3
4
5
6
// 메모리에서 count 값을 레지스터로 옮긴다
register2 = count
// 레지스터에서 산술 연산을 한다.
register2 = register2 - 1 
// 메모리로 레지스터 값을 옮긴다
count = register2

그렇다면 count값이 초기에 10으로 시작했다고 가정하면 아래와 같은 상황이 벌어질 수 있다.

1
2
3
4
5
6
생산자 로직 : register1 = count   {register1 = 5}
생산자 로직 : register1 = register1 + 1   {register1 = 6} 
소비자 로직 : register2 = count   {register2 = 5} 
소비자 로직 : register2 = register2 - 1   {register2 = 4} 
생산자 로직 : count = register1   {count = 6} 
소비자 로직 : count = register2   {count = 4}

생산자 다음에 소비자 로직이 실행되었다면 5 -> 6-> 5 순서대로 count 값이 세팅되어야 정상이겠지만 레지스터로 옮기고 연산하는 과정에서 제대로 정합성 유지가 안되는 것을 알 수 있다.

이러한 문제를 해결하기 위해서는 스레드의 순차 실행을 보장해줘야한다. 그러기 위해서는 임계구역에 대해서 알아야한다.

임계구역 (Critical section)

경쟁상태가 발생할 수 있는 코드 영역을 임계 구역이라고 한다. 이러한 경쟁 상태가 발생할 수 있는 부분은 멀티 스레드나 멀티 프로세스 환경에서는 공유 변수를 변경하거나 전역 테이블을 업데이트하거나 파일을 쓰는 등의 작업을 수행하는 곳에서 일어난다.

문제 해결 요구 사항

1. 상호배제(Mutual Exclusion)

임계 구역에 접근 가능한 스레드는 한번에 한 개이다.

2. 진행(Progress)

자신의 진입 영역 또는 임계 구역이 아닌 영역에서 실행 중인 스레드만이 임계영역에 접근할 스레드 대상 리스트에 참여할 수 있다.

3. 유한 대기 (Bounded Waiting)

스레드가 자신의 임계 구역에 들어가려는 요청을 한 후에 그 요청이 허용되기 전까지 다른 스레드들이 자신의 임계 구역에 들어가는 횟수에는 한계나 제한이 존재한다. (무한 대기를 막기 위함)

참고 자료

This post is licensed under CC BY 4.0 by the author.