스레드 동기화

이지우·2023년 6월 8일
0

운영체제

목록 보기
2/3
  1. 스레드 동기화 필요성
  2. 상호 배제
  3. 멀티스레드 동기화 기법
  4. 생산자 소비자 문제

스레드 동기화 필요성

다수의 스레드가 동시에 공유 데이터에 접근할 경우 공유 데이터가 훼손될 수 있음!
스레드 동기화
: 공유데이터 훼손되는 문제 해결책
:공유 데이터를 접근하고자 하는 다수의 스레드가 충돌 없이 공유데이터에 접근하기 위해 상호 협력하는 것
-> 한 스레드가 공유데이터를 배타적 독점적으로 접근

공유 집계판

공유 데이터에 대한 멀티스레드의 접근 문제와 동일함
스레드 동기화를 통해 한 스레드가 공유데이터에 대해 배타적이고 독립적으로 접근하도록 허용되어야 함

스레드 동기화
한 스레드가 공유 데이터 사용을 마칠 때까지 다른 스레드가 접근하지 못하도록 제어하는 기법

공유 데이터 액세스

공유 집계판 사례를 멀티스레드로 구현하여 공유데이터가 훼손되는 상황 확인

  • 2개의 worker 스레드
  • 공유 변수 sum
  • sum에 10을 더하는 코드를 1,000,000번씩 실행
  • sum이 최종적으로 20,000,000이 될 것으로 예상
#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("total sum = %d\n", sum); // 두 스레드의 종료 후 sum 출력

	return 0;
}

실행할 때마다 sum 값이 달라지며, 20,000,000이 되지 않음


공유 데이터에 대한 동시 접근 문제의 해결책
문제점: 여러 스레드가 공유 데이터에 동시 접근하면 훼손 가능성 있음

해결책: 스레드 동기화; 한 스레드가 접근 마칠 때까지 다른 스레드가 접근 못하도록 제어

임계구역과 상호배제

다수의 스레드가 공유 데이터를 사용하려고 할 때, 먼저 접근한 스레드가 공유 데이터를 배타적으로 사용하도록 다른 스레드가 접근하지 못하게 상호 협력함

임계구역(critical section)
: 사용자가 작성한 프로그램 중 공유 데이터에 접근하는 코드 블록
: 반드시 한 스레드만 배타적 독점적으로 실행하도록 관리되어야 함

상호배제(mutual exclusion)
: 임계구역이 오직 한 스레드만 배타적(독점적)으로 사용되도록 하는 기술
: 임계구역에 먼저 진입한 스레드가 임계구역의 실행을 끝낼 때까지 다른 스레드가 진입하지 못하도록 보장


상호배제

상호배제
: 멀티스레드가 실행되는 환경에서, 한 스레드가 임계구역 전체를 배타적으로 실행하도록 보장하는 기법

위치

임계구역 전후에 상호배제 코드가 작성됨

  • 일반 코드(non-critical code)
    : 공유 데이터를 액세스하지 않는 코드 부분

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

  • 임계구역 코드(critical code)
    : 공유 데이터에 접근하는 코드 블록
    : 한 번에 한 스레드만 실행하도록 보장되어야 하는 프로그램 부분
    : 짧을수록 좋으므로 공유 데이터를 액세스하는 최소한의 코드만 임계구역으로 만듬

  • 임계구역 진출 코드(exit code)
    : 상호배제를 위해 필요한 코드 (임계구역의 실행을 마칠 때 실행되어야 하는 코드 블록)
    : entry code 에서 대기중인 스레드가 임계구역에 진입할 수 있도록 entry code 에서 취한 조치를 해제하는 코드

상호배제 요구조건
1. 오직 하나의 스레드만 임계역역에 진입할 수 있다
2. 임계영역이 아닌 곳에서 수행이 멈춘 스레드는 다른 스레드의 수행을 간섭하면 안된다
3. 임계영역에 접근하고자 하는 스레드의 수행이 무한히 미뤄지면 안된다
☞ 교착상태(deadlock) / 기아(starvation) 일어나면 안됨
4. 임계영역이 비어 있을 때, 진입하려고 하는 스레드가 지연되면 안된다
5. 프로세서의 개수나 상대적인 스레드 수행 속도에 대한 가정은 없어야 한다
6. 스레드는 유한 시간 동안만 임계영역에 존재할 수 있다

구현

임계구역에 오직 1개의 스레드만 진입

  • SW 방법 - Peterson's 알고리즘
  • HW 방법 - 인터럽트 서비스 금지, 원자 명령 활용

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

임계구역 entry 코드에서 인터럽트 서비스를 금지하는 명령 실행
exit 코드에서 인터럽트를 허용하는 CPU 명령 실행
☞ 임계구역 실행하고 있는 동안 인터럽트 발생하지 않아 선점(preemption)되지 않음

문제점

  • 모든 인터럽트가 무시됨
  • 멀티 코어 CPU나 다중 CPU에서 활용 불가
    : 두 스레드가 서로 다른 코어에서 실행되면 한 코어의 인터럽트만 금지 가능

방법2) 원자명령 사용

원자명령: 상호배제를 위해 만들어진 CPU 명령


원자명령 없이 lock 변수를 이용한 상호배제 시도

임계구역 들어갈 때 lock = 1 나올 때 lock = 0
entry 코드에서 lock = 1, exit 코드에서 lock = 0

lock 변수를 이용한 상호배제의 근본적인 문제

원자명령 이용

lock 변수를 사용한 경우 상호배제가 실패한 원인
entry 코드에서 lock 변수 값을 읽는 명령과 lock 변수에 1을 저장하는 명령 사이에 컨텍스트 스위치이 될 때 문제 발생

=> lock 변수를 읽어 들이는 명령과 lock 변수에 1을 저장하는 명령을 한번에 처리하는 원자 명령(TSL) 필요


멀티스레드 동기화 기법

멀티스레드 동기화
: 상호배제 기반
: 자원을 사용하려는 여러 스레드들이 자원을 원활히 공유하도록 하는 기법

  • locks 방식 - 뮤텍스(mutex), 스핀락(spinlock)
    : 상호배제가 되도록 만들어진 락(lock) 활용
    : 락을 소유한 스레드만 임계구역 진입
    : 락을 소유하지 않은 스레드는 락이 풀릴 때까지 대기

  • wait-signal 방식 - 세마포(semaphore)
    : n개의 자원을 사용하려는 m개 멀티스레드의 원활한 관리
    : 자원을 소유하지 못한 스레드는 대기(wait)
    : 자원을 다 사용한 스레드는 알림(signal)

뮤텍스

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

구성 요소

  1. 락 변수
    : true/false 중 하나
    : true - 락을 잠근다. 락을 소유한다.
    : false - 락을 연다. 락을 해제한다.

  2. 대기 큐
    : 락이 열리기를 기다리는 스레드 큐

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

특징

스핀락

세마포

멀티스레드 사이의 자원 관리 기법

  • n개의 공유 자원을 다수 스레드가 공유하여 사용하도록 돕는 자원 관리 기법 / 동기화 기법
  • 상호 배제를 운영체제와 프로그래밍 언어 수준에서 지원하는 메커니즘
  • 블록(수면)과 깨움을 지원
  • 예) 주차장 전광판에 주차가능 대수가 세마포어 변수 s 값 비유

구성 요소

  1. 자원
    : n개

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

  3. counter 변수
    : 사용 가능한 자원 개수 (n으로 초기화)

  4. P/V 연산
    P연산(wait 연산) - 자원 요청 시 실행 (자원 사용 허가)
    V연산(signal 연산) - 자원 반환 시 실행 (자원 사용 끝 알림)

P 연산 : 자원 사용 허가, 사용 가능 자원 수 1 감소 (counter--)
V 연산 : 자원 사용 마침, 사용 가능 자원 수 1 증가 (counter++)

세마포 종류 (자원 할당받지 못한 경우의 행동에 따라 구분)

  • sleep-wait(수면 대기) 세마포
    P연산: 대기 큐에서 잠자기
    V연산: 사용가능 자원이 있으면 잠자는 스레드 깨워서 사용 허락

  • busy-wait(바쁜 대기) 세마포
    P연산: 사용 가능 자원이 생길 때까지 무한 루프
    V연산: counter--

이진 세마포

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


생산자 소비자 문제

응용프로그램에 존재하는 사례

문제 정의

  • 공유 버퍼를 사이에 두고, 공유 버퍼에 데이터를 공급하는 생산자들과
  • 공유 버퍼에서 데이터 읽고 소비하는 소비자들이 공유 버퍼를 사용할 때,
  • 공유 버퍼를 문제 없이 사용하도록 생산자와 소비자를 동기화시키는 문제
  • 멀티스레딩 응용프로그램 작성 시 자주 발생

해결해야되는 문제

  1. 상호 배제 해결
    생산자들과 소비자들의 공유 버퍼에 대한 상호 배제
  2. 비어 있는 공유 버퍼 문제
    비어 있는 공유 버퍼를 소비자가 읽을 때
  3. 꽉찬 공유 버퍼 문제
    꽉찬 공유 버퍼에 생상자가 쓸 때

해결책

비어 있는 버퍼 문제

꽉찬 공유 버퍼 문제

profile
노력형 인간

0개의 댓글