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

김진웅·2023년 11월 24일

Operating System

목록 보기
2/3
post-thumbnail

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


  • 스레드 동기화의 필요성

    • 다수의 스레드가 동시에 공유 데이터에 쓰기를 접근하는 경우에 공유데이터가 훼손되는 문제 발생 가능
      • 두 스레드가 동시에 공유 데이터를 읽는 경우 -> 문제 없음
      • 한 스레드는 쓰고 한 스레드는 읽을 경우 -> 읽고 쓰는 순서에 따라 읽는 값이 달라질 수 있지만 공유데이터의 훼손은 없음
      • 두 스레드가 동시에 공유 데이터에 쓰는 경우 -> 공유 데이터 훼손 가능성

    • 스레드 동기화(thread synchronization)
      • 공유데이터에 대한 다수의 스레드가 동시에 접근할 때 공유데이터가 훼손되지 않게 하는 기법
        • 한 스레드가 공유데이터를 배타적 독점적으로 접근하도록 순서화 하는것

  • 공유 집계판에 동시 접근하는 사례


    위 문제를 C언어 코드로 작성하면
#include <stdio.h>
#include <pthread.h>

int sum = 0; // 두 스레드가 공유하는 변수

void* worker(void* arg) { // 스레드 코드
	for (int i = 0; i < 1000000; i++) {
		sum = sum + 10;
	}
}
int main() {
	char* name[] = { "박지성", "손흥민" };
	pthread_t tid[2]; // 2개의 스레드 ID를 담을 배열
	pthread_attr_t attr[2]; // 2개의 스레드 정보를 담을 배열

	pthread_attr_init(&attr[0]); // 디폴트 속성으로 초기화
	pthread_attr_init(&attr[1]); // 디폴트 속성으로 초기화

	pthread_create(&tid[0], &attr[0], worker, name[0]); // 스레드 생성
	pthread_create(&tid[1], &attr[1], worker, name[1]); // 스레드 생성

	pthread_join(tid[0], NULL);
	pthread_join(tid[1], NULL);

	printf("sum = %d\n", sum); // 두 스레드 종료 후 sum 출력

	return 0;
}

위 코드를 실행하면 아래와 같은 결과가 발생한다.

total sum이 20000000이 되어야 정상인데, 공유 변수 sum에 대한 두 스레드의 충돌로 인해, 20000000이 안되는 문제가 발생했다.

  • 공유 데이터 접근 문제의 해결책

    • 문제점
      • 여러 스레드가 공유 변수에 접근할 때, 공유 데이터 훼손
    • 해결책 - 스레드 동기화
      • 한 스레드가 공유 데이터 사용을 마칠 때까지,
      • 다른 스레드가 공유 데이터에 접근하지 못하도록 제어
    • 멀티스레드의 경쟁 상황은 매우 자주 발생
      • 사용자의 멀티스레드 프로그램에서 자주 발생
      • 커널 코드에서 매우 자주 발생
        -> 커널에 공유 데이터가 많기 때문에
      • 다중 코에어서는 더욱 조심해야함


  • 임계구역과 상호배제


    • 임계구역(critical section)

      • 공유 데이터에 접근하는 프로그램 코드들

    • 상호배제(mutual exclusion)

      • 임계구역을 오직 한 스레드만 배타적 독점적으로 사용하도록 하는 기술
        - 임계구역에 먼저 진입한 스레드가 임계구역의 실행을 끝낼 때까지,
        - 다른 스레드가 진입하지 못하도록 보장





2. 상호 배제(mutual exclusion)

  • 상호 배제를 포함하는 전형적인 프로그램 모습


    1. 일반 코드(non-critical code)

      • 공유 데이터를 액세스하지 않는 코드
    2. 임계구역 진입 코드(entry code)

      • 임계구역에 진입하지 전 필요한 코드 블록
      • 현재 임계구역을 실행 중인 스레드가 있는지 검사
        • 없다면, 다른 스레드가 들어오지 못하도록 조치
        • 있다면, 진입이 가능해질 때까지 대기
    3. 임계구역 코드(critical code)

    4. 임계구역 진출 코드(exit code)

      • 임계구역을 마칠 때 필요한 코드 블록
      • 대기중인 스레드가 임계구역에 진입할 수 있도록, 진입 코드에서 취한 조치 해제

  • 상호배제 구현

    • 상호배제 구현 이유
      • 임계구역에 오직 1개의 스레드만 진입하게 하기 위해

    • 상호배제 구현 방법
      • 소프트웨어적 방법 - Peterson's 알고리즘 등
        • 알고리즘 수준에서 제시된 것들로 구현 시 여러 문제 노출
      • 하드웨어적 방법 - 하드웨어의 도움을 받는 방법
        • 인터럽트 서비스 금지, 원자 명령 활용 등
        • 오늘날 대부분 하드웨어적 방법 사용

    • 하드웨어적 방법
      • 방법 1 - 인터럽트 서비스 금지
        • 인터럽트 서비스를 금지하거나 허용하는 CPU 명령 사용
      • 방법 2 - 원자 명령(atomic instruction) 사용
        • 원자 명령은 CPU 명령임
        • 오늘날 상호배제 구현에 사용하는 방법

  • 상호배제구현 1 - 인터럽트 서비스 금지

    • 방법

      • entry 코드에서 인터럽트 서비스를 금지하는 명령 실행
        • 장치로부터 인터럽트가 발생해도, CPU가 인터럽트 발생을 무시
        • 인터럽트가 발생해도 CPU는 인터럽트 서비스 루틴을 실행하지 않음
        • 인터럽트를 무시하면 임계구역을 실행하는 스레드가 중단되지 않음

    • 문제점
      - 모든 인터럽트가 무시되는 문제 발생
      - 멀티 코어 CPU나 다중 CPU를 가진 시스템에서 활용 불가
      - 한 CPU의 인터럽트 금지로 다른 CPU에게 인터럽트를 금지시킬 수 없음
      -> 현재 CPU의 인터럽트 금지가 다른 CPU에게는 영향을 미치지 않음
      - 다른 CPU가 타이머 인터럽트 서비스 루틴을 실행하여, 임계구역을 실행중인 스레드를 컨텍스트 스위칭시키고 다른 스레드를 스케줄할 수 있음



    • lock 변수를 이용한 상호배제
      - lock 변수 : 1이면 잠금 상태
      - lock 변수 : 0이면 열린 상태

    • 위 문제를 해결할 수 있는 방법은 원자명령을 도입하는 것이다.

      • 원자 명령(atomic instruction)
        • lock 변수를 읽어 들이는 명령과 lock 변수에 1을 저장하는 2개의 명령을 한 번에 처리하는 원자명령 필요

        • 원자명령 : TSL(Test and Set Lock)



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

  • 멀티스레드 동기화

    • 정의
      • 상호배제 기반위에, 자원을 사용하려는 여러 스레드들이 자원을 원활히 공유하도록 하는 기법
      • 동기화 프리미티브(synchronization primitives)로 부름

    • 대표적인 기법
      • locks 방식 : 뮤텍스(mutex), 스핀락(spinlock)
        • 상호배제가 되도록 만들어진 락(lock) 활용
        • 락을 소유한 스레드만이 임계구역 진입
        • 락을 소유하지 않은 스레드는 락이 풀릴 때까지 대기
      • wait-signal 방식 : 세마포(semaphore)
        • n개 자원을 사용하려는 m개 멀티스레드의 원활한 관리
        • 자원을 소유하지 못한 스레드는 대기(wait)
        • 자원을 다 사용한 스레드는 알림(signal)

  • 뮤텍스(뮤텍스 기법)

    • 뮤텍스(mutex) 기법

      • 잠김/열림 중 한 상태를 가지는 락 변수 이용
      • 한 스레드만 임계구역에 진입시킴
      • 다른 스레드는 큐에 대기
      • sleep-waiting lock 기법

    • 뮤텍스 기법의 구성 요소

      1. 락 변수

        • true/false 중 한 값
        • true : 락을 잠근다. 락을 소유한다.
        • false : 락을 연다. 락을 해제한다.
      2. 대기 큐

        • 락이 열리기를 기다리는 스레드 큐
      3. 연산

        • lock 연산(임계구역의 entry 코드)
          • 락이 열린 상태이면, 락을 잠그고 임계구역 진입
          • 락이 잠김 상태이면, 현재 스레드를 블록 상태로 만들고 대기 큐에 삽입
        • unlock 연산(임계구역의 exit 코드)
          • lock = false, 락을 열린 상태로 변경
          • 대기 큐에서 기다리는 스레드 하나 깨움

    • 뮤텍스 기법의 특징

      • 임계구역의 실행 시간이 짧은 경우, 비효율적
        • 락이 잠겨 있으면(컨텍스트 스위칭되어) 대기 큐에서 대기, 락이 풀리면 다시 (컨텍스트 스위칭되어) 실행

        • 락이 잠겨있는 시간보다 스레드가 잠자고 깨는 데 걸리는 시간이 상대적으로 크면 비효율적


  • 스핀락(스핀락 기법)

    • 스핀락(spinlock) 기법

      • busy-waiting lock 기법
        • 스레드가 큐에서 대기하지 않고 락이 열릴 때까지 계속 락 변수 검사
      • 뮤텍스와 거의 같고 busy-waiting이라는 점에서만 다름
        • 대기 큐 없음
        • busy-waiting으로 인해 CPU를 게속 소모, CPU가 다른 스레드를 실행할 수 없음
      • 락을 소유한 스레드만 자원 배타적 사용, 동기화 기법
        • 공유 자원 하나 당 하나의 스핀락 사용

    • 스핀락 기법의 구성 요소

      1. 락 변수
        • true/false 중 한 값
        • true : 락을 잠근다. 락을 소유한다.
        • false : 락을 연다. 락을 해제한다.
      2. 연산
        • lock 연산
          • 임계구역에 들어갈 때 실행되는 entry 코드
          • 락이 잠김 상태면, 락이 풀릴 때까지 무한 루프 돌면서 lock 연산 시도
          • 락이 열린 상태면, 락을 잠김 상태로 바꾸고 임계구역 실행

        • unlock 연산
          • 임계구역을 나올 때 실행하는 exit 코드

          • 락을 열림 상태로 변경


    • 스핀락 기법의 특징

      • 뮤텍스의 non-blocking 모델
        • 락이 잠겨 있을 때 블록되지 않고 락이 풀릴 때까지 검사하는 코드 실행
      • 단일 CPU(단일 코어)를 가진 운영체제에서 비효율적
        • 단일 코어 CPU에서 의미 없는 CPU 시간 낭비
          • 스핀락을 검사하는 스레드의 타임 슬라이스가 끝날 때까지 다른 스레드 실행 안 됨
          • 락을 소유한 다른 스레드가 실행되어야 락이 풀림
        • 멀티 코어에 적합
          • 한 코어에서 임계구역을 실행 중일 때, 다른 코어에서 락이 풀릴 때까지 검사


  • 뮤텍스와 스핀락 비교

    1. 락이 잠기는 시간이 긴(임계구역이 긴) 경우

      • 뮤텍스가 효율적
        • 락을 얻지 못했을 때, CPU를 다른 스레드에게 양보하는 것이 효율적
    2. 단일 CPU를 가진 시스템

      • 뮤텍스가 효율적
    3. 멀티 코어(멀티 CPU)를 가진 시스템

      • 스핀락이 효율적
        • 임계구역은 보통 짧게 작성되므로, 잠자고 깨는 컨텍스트 스위칭 없이 바로 자원 사용
    4. 사용자 응용프로그램과 커널 코드

      • 각각 뮤텍스와 스핀락이 효율적
        • 커널 코드나 인터럽트 서비스 루틴은 빨리 실행되어야 함
        • 인터럽트 서비스 루틴 내에서 잠잘 수 없음
    5. 스핀락을 사용하면 기아 발생 가능

      • 스핀락은 무한 경쟁 방식이어서 기아 발생 가능

  • 세마포(세마포 기법)

    • 세마포(semaphore) 기법

      • 멀티스레드 사이의 자원 관리 기법
        • n개의 공유 자원을 다수 스레드가 공유하여 사용하도록 돕는 자원 관리 기법

    • 세마포 기법의 구성요소

      1. 자원 : n개

      2. 대기큐 : 자원을 할당받지 못한 스레드들이 대기하는 큐

      3. counter 변수

        • 사용 가능한 자원의 개수를 나타내는 정수형 전역 변수
        • n으로 초기화
      4. P/V 연산

        • P연산(wait 연산) - 자원 요청 시 실행하는 연산(자원 사용 허가)

        • V 연산(signal 연산) - 자원 반환 시 실행하는 연산(자원 사용 종료)

        • 자원을 할당받지 못한 경우의 행동에 따라 sleep-wait/busy-wait 세마포로 구분

          • sleep-wait 세마포
            • P연산 : counter--, 대기 큐에서 잠자기
            • V연산 : counter++, 사용가능 자원이 있으면 잠자는 스레드 깨우기
          • busy-wait 세마포
            • P연산 : 사용 가능 자원이 생길 때까지 무한 루프 후 자원이 생기면 counter--
            • V연산 : counter++



출처

명품 운영체제 (저자 황기태)

profile
IT Velog

0개의 댓글