Post

[운영체제] 6장 스레드 동기화

스레드 동기화

1. 스레드 동기화의 필요성

여러 스레드가 동시에 같은 변수에 접근하여 데이터를 조작할 경우 원치 않는 결과가 초래됨

  • 이를 해결하기 위해 공유 데이터에 대한 다수 스레드의 동시 접근을 해결하는 방법이 스레드 동기화이다.

1.1 공유 집계판 문제

공유집계판

스레드 동기화를 통해 여러 스레드가 동시에 공유 데이터에 접근하는 것을 막는다.

1
스레드 동기화란 다수의 스레드가 공유 데이터를 동시에 쓰는 충돌 상황에서 공유 데이터가 훼손되지 않도록 스레드의 실행을 제어하는 기법이다.

1.2 공유 데이터 액세스 문제와 해결방법

| 공유변수모델 | 공유변수문제 | | :———————————————————-: | :———————————————————-: |

두 스레드가 동시에 공유 데이터에 접근(read, write 실행)

공유 데이터에 대한 동시 접근 문제의 해결책
  • 문제점 : 여러 스레드가 공유 데이터에 동시에 쓰기를 수행하면 공유 데이터가 훼손될 수 있다.
  • 해결책: 스레드 동기화. 한 스레드가 공유 데이터에 대한 사용을 마칠 때까지 다른 스레드가 접 근하지 못하도록 제어한다.

1.3 임계구역과 상호배제

  • 공유 데이터에 접근하는 코드 블록 : 임계구역
  • 임계구역을 한 스레드만 배타적 독점적으로 실행하도록 관리하는 것 : 상호배제

2. 상호배제 (매우 중요)

한 스레드가 임계구역 전체를 배 타적으로 실행하도록 보장하는 기법이다

2.1 상호배제 위치

상호배제위치

  • 일반 코드(non—critical 코드)
    • 공유 데이터를 액세스하지 않는 부분이다.
  • 임계구역진입코드(entry 코드)
    • 임계구역을 이미 실행 중 인 스레드가 있는지 검사하고, 없는 경우 다른 스레드가 들어오지 못하도록 조치를 취한다
  • 임계구역 진출코드(exit 코드)
    • exit 코드는 스레드가 임계구역 실행을 마칠 때 실행되어야 하는 코드 블록으로, entry= 에서 대기 중인 스레드가 임계구역에 진입할 수 있도록 은ntry 코드에서 취한 조치를 해제한다
  • 임계구역 코드(critical 코드)
    • 한 번에 한 스레드만 실행하도록 보장되 어야 하는 프로그램 부분이다

2.2 상호배제 구현

임계구역에 대한 상호배제는 ‘임계구역에 오직 한 스레드만 들어가게 하는 방책’이다

  • 소프트웨어적 방법 - Peterson’s 알고리즘
  • 하드웨어적 방법 - 인터럽트 금지, 원자명령 활용

2.3 방법 1 - 인터럽트 서비스 금지

임계 구역에 들어갈 때 entry 코드에서 인터럽트 서비스를 금지하고, exit 코드에서 인터럽트를 허용하는 CPU 명령들을 실행한다.

cli ; entry 코드
...
임계구역코드
...
sti	; exit 코드

cli 명령은 CPU 내부 인터럽트 플래그를 0으로 리셋 → 인터럽트 금지

sti 명령은 CPU 내부 인터럽트 플래그를 1으로 설정 → 인터럽트 허용

clisti

인터럽트 서비스를 막는 방법에는 두 가지 문제점이 있다.

  • 임계구역 코드가 실행되는 동안 cli를 통해 인터럽트를 막았기 때문에 다른 중요한 ISR이 실행될 수 없다.
  • 멀티 코어 CPU에서는 활용할 수 없다. Notes_240417_220511

2.4 방법 2 - 원자명령(atomic instruction) 사용

인터럽트를 금지시키는 것은 불완전하여 원자명령을 이용한다.

원자명령 : 상호배제를 위해 만들어진 CPU 명령 (CPU마다 명령이 서로 다름)

먼저 원자명령 없이 lock 변수를 이용한 경우를 보면,

성공한 경우실패한 경우
lock사용lock

lock 변수를 읽어와서 flag을 1로 설정한다. 다른 스레드에서 이에 접근하면 0인지를 확인하고 아닌 경우 lock의 값이 1이 될 때 까지 반복한다. 임계영역 실행을 마친 스레드는 마지막에 lock을 1으로 세팅하여 다른 스레드가 임계영역 코드를 사용할 수 있게 한다.

→ lock도 변수이기 때문에 앞서 본 것 처럼 lock 변수를 읽고 쓰는 시점에 따라 임계구역 충돌이 발생할 수 있다.

2.5 원자명령을 이용한 상호배제

lock 변수를 사용한 경우 상호배제가 실패한 원인은

lock 변수를 읽는 명령, 쓰는 명령이 나뉘어져 있어 명령어가 실행되는 시점에 따라 상호배제 실패가 발생할 수 있다.

해결 방법

이를 해결하는 매우 간단한 방법이 존재하는데 이는 두 명령을 하나로 합쳐 명령 사이 컨텍스트 스위칭을 막는 것이다. 이를 원자명령 혹은 TSL 이라고 한다.

원자명령

※ TSL을 원자명령이라 명명한 것은, 2개의 명령을 합쳐 1개의 명령으로 만들고 TSL 명령 실행 중간에 인 터럽트 서비스나 컨텍스트 스위칭이 발생하지 못하도록 하였다는 의미

3. 멀티스레드 동기화 기법

다음은 공유 자원을 문제 없이 사용하기 위해 대표적인 3가지 기법이다.

  • 락(lock) 방식 - 뮤텍스(mutex), 스핀락(spinlock)
  • wait-signal 방식 - 세마포(semaphore)

3.1 뮤텍스

뮤텍스

잠김/열림(lockedAmlocked) 중 한 상태를 가지는 락 변수를 이용하여, 한 스레드만 임계구역에 진입시키고 다른 스레드들을 큐에 대기시 키는 기법이다

이를 위해 락 변수, 연산, 큐가 필요하다.

뮤텍스동기화

뮤텍스의 특징

  • 임계 구역의 실행 시간이 짧은 경우 뮤텍스는 비효율적이다.
  • 락 되는 시간 > 스레드가 자고 깨는데 걸리는 시간(컨텍스트 스위칭)

3.2 스핀락(spinlock)

스핀락

뮤텍스와 같이 락을 기반으로 하지만, 뮤텍스와 달리 대기 큐가 없다

락변수

스핀락을 소유한 한 개의 스레드만 임계구역에 진입할 수 있다

lock/unlock 연산

entry와 exit에 해당하는 명령

스핀락동기화

  • 스핀락은 mutex의 바쁜 대기 모형이다. unlock 까지 계속 검사하는 코드를 실행한다.
  • 단일 CPU에서 비효율적이다. T1이 끝나야 unlock이 되는데 T2은 타임 슬라이스 내내 의미 없는 lock 검사만 진행하여 다른 스레드의 실행 기회마저 뺏는다.
  • 임계구역 코드가 짧은 응용에서 효과적 (컨텍스트 스위칭 및 스케줄링 필요 없음)
  • 스레드 간 lock 경쟁으로 인해 기아가 발생할 수 있다.

※ 리눅스 커널은 스레드 동기화의 기본 기법으로 스핀락을 사용함

 뮤텍스스핀락
대기 큐OX
블록 가능 여부lock이 잠긴 상태면 블록됨잠긴 상태에서 블록되지 않고 계속 검사
lock / unlock 연산비용저비용CPU 계속 사용하므로 고비용
하드웨어 관련단일 CPU에 적합멀티코어 CPU에 적합
주 사용처사용자 응용 프로그램커널 코드, ISR

3.3 세마포(Semaphore)

Semaphore

세마포는 n개의 자원을 다수의 스레드가 공유하여 사용하도록 돕는 자원 관리 기법이다.

세마포

스레드는 세마포에 자원 요청을 허락 받아 사용한다. 자원이 모두 사용중이라면 세마포는 해당 스레드를 대기 큐에서 재우고, 자원을 사용하던 스레드가 자원 반환하여 세마포에 알리면 세마포는 대기 큐에서 한 스레드를 깨워 자원 사용을 허가한다.

  • 자원 - N개

  • 대기 큐 - 자원을 할당 받지 못한 스레드가 잠자는 곳

  • counter 변수 (세마포 변수) - 사용 가능한 자원의 개수를 나타냄 / 자원의 개수로 초기화 됨

    ​ counter < 0 : 큐에서 대기 중인 스레드의 개수

    ​ counter = 0 : 자원 모두 사용 중

  • P/V 연산 - P 연산은 자원 요청 시, V 연산은 자원 반환 시 실행 됨

세마포2

P / V 연산
  • P 연산 : 스레드에게 자원 사용 허가과정 - counter–
  • V 연산 : 스레드가 자원 사용 끝을 세마포에 알리는 과정 - count++
수면 대기 세마포
1
2
3
4
5
6
P 연산 { // wait
    counter--; // 자원 요청
    if (counter < 0){
        ... 스레드를 대기큐에 삽입 // sleep-wait
    }
}
1
2
3
4
5
6
7
V 연산 { // signal
    counter++; // 자원 반환
    if counter <= 0 { // 자원을 요청 받고 싶어하는 스레드가 있음
    ... 대기 큐에서  스레드 깨움...
    }
}

바뜬 대기 세마포
1
2
3
4
P 연산 { // wait
    while counter <= 0; // busy-wait
    counter--;
}
1
2
3
V 연산 { // signal
	counter++;
}

※ counter 역시 공유 변수이므로 원자 명령을 통해 동시 접근 불가능

3.4 이진 세마포

  • 카운터 세마포 - 자원이 여러개
  • 이진 세마포 - 자원이 1개
이진 세마포 구성요소
  • 세마포 변수 S : 0 또는 1
  • 대기 큐 : 동일
  • P 연산 : 동일
  • V 연산 : 동일 (S <= 0 이면 하나 깨움)

3.5 동기화 이슈 : 우선순위 역전

4. 생산자 소비자 문제

생산자 소비자 문제는 많은 응용프로그램에서 발생하는 전형적인 동기화 문제

미디어플레이어
• 입력스레드와 재생스레드가 비디오 버퍼를 동시에 접근하는 경우의 상호배제
• 재생스레드가 읽으려고 했을 때 비디오 버퍼가 비어있는 문제 - 네트워크의 지연 등으로 입력스레 드에 의한 프레임 공급이 늦어지고 있는 상황
• 입력스레드가 쓰려고 했을 때 비디오 버퍼가 꽉 찬 문제 - 재생스레드가 비디오 버퍼를 비워내는 속도보다 더 빠르게 입력스레드에 의해 프레임이 채워지는 경우

4.2 생산자 소비자 문제의 정의

유한한 크기의 공유 버퍼에 데이터를 공급하는 생산자와 공유버퍼에서 데이터 읽고 소비하는 소비자가 공유 버퍼를 문제없이 사용하도록 생산자와 소비자를 동기화시키는 (실행 순서를 제어 하는) 문제이다.

  • 문제 1 - 상호 배제 (공유 버퍼 사용에 대한)
  • 문제 2 - 비어 있는 공유버퍼 문제
  • 문제 3 - 꽉찬 공유 버퍼 문제
상호배제 해결
  • 임계구역에 대한 상호 배제 → 뮤텍스, 세마포로 해결
비어 있는 공유 버퍼 문제 해결

소비자 스레드는 대기 (wait)하고, 생산자 스레드는 대기상태에서 깨어나도록 알리는(signal) 방식이므로, 스레드 동기화를 위해 세마포가 적당함

소비자생산자

R은 버퍼 내 프레임 개수

소비자가 P 연산 시 R을 확인하여 R이 0이면 잠을자고 생산자 스레드를 깨워 버퍼를 채우고 V 연산을 통해 R을 1 증가시키고 소비자 스레드를 깨운다. 이후 소비자 스레드는 P 연산의 남은 부분을 실행하고 버퍼를 읽는다. 소비자의 P 연산이 끝나면 R은 다시 0이 될 것이다.

꽉 찬 공유버퍼 문제 해결

소비자생산자2

W은 버퍼 내 빈 공간

생산자가 버퍼에 쓰기 위해 P 연산을 하면 잠을자고 소비자 스레드를 깨워 버퍼에서 데이터를 읽어 V 연산을 진행하면 W을 1 증가 시키고 생산자 스레드를 깨운다. 이후 생산자 스레드는 P 연산의 남은 코드를 실행하고 버퍼에 쓴다. 이 때 생산자의 P 연산이 끝나면 W는 다시 0이 될 것이다.

※ 위에서 본 counter와 증감이 다름 유의

생산자와 소비자 알고리즘

  • 세마포 R : 읽기 가능한 버퍼 개수 0이면(프레임의 개수) 대기
  • 세마포 W : 쓰기 가능한 버퍼 개수 0이면(빈 공간의 개수) 대기
  • 뮤텍스 M : 임계구역 코드의 상호배제를 위함
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
Consumer { // 소비자
    while(true) {
        P(R); // 프레임이 있는지 확인하고 없으면 대기
        
        뮤텍스(M) 잠근다.
        공유버퍼에서 데이터를 읽는다. // 임계구역 코드
        뮤텍스(M) 연다.
            
        V(W); // 빈 공간 하나 증가
    }
}
Producer { // 생산자
    while(true) {
        P(W); // 빈 공간 있는지 확인하고 없으면 대기
        
        뮤텍스(M) 잠근다.
        공유버퍼에 데이터를 저장한다. // 임계구역 코드
        뮤텍스(M) 연다
            
        V(R); // 프레임 하나 증가
    }
}
This post is licensed under CC BY 4.0 by the author.