Critical Section -1-

mjdevv·2023년 12월 24일
0

운영체제

목록 보기
9/12

아래의 동시성 문제 예제 코드를 통해 동시성 문제가 무엇인지, critical section이 무엇인지 알아보자.


동시성 문제 예제

#include <stdio.h>
#include <pthread.h> 

int sum; 

void *run1(void* param){
	int i;
    for(int i=0; i<10000; i++)
    	sum++;
	pthread_exit(0);//thread exit 
}

void *run2(void* param){
	int i;
    for(int i=0; i<10000; i++)
    	sum--; 
    pthread_exit(0);
}

int main(){
	phtread_t tid1, tid2;//thread tid1,tid2 생성  
    
    pthread_create(&tid, NULL, run1, NULL); 
    pthread_create(&tid, NULL, run2, NULL); 
    
    pthread_join(tid, NULL);
    pthread_join(tid, NULL);

	printf("%d \n", sum); 
}

위의 예제 코드를 해석보자.

  1. data 메모리 영역의 공유 자원 sum 변수에
  2. tid1, tid2 스레드가 각각 접근
  3. 각자 ++, --연산을 시행

각자 10000번 더하고 10000번 빼니까 독립적으로 시행된다고 가정해보면 0이 나와야 한다. 하지만 실제로 프로그램을 실행해보면 예상 외의 결과가 나온다.

항상 같은 값을 출력하는 게 아니라, 랜덤한 값들이 나오게 된다. 왜 그럴까? 위 코드의 기계어 슈도코드를 보면 알 수 있다.


예제 코드의 동시성 문제의 원인

//tid1 기계어 슈도 코드 
... 
register1 = sum; // 데이터 영역 sum 변수의 값을 tid1의 register1으로 복사
register1 = register1+1; //register1에서 연산 시행 
sum = register1; //register1에서 연산한 값을 데이터 영역 sum 변수 값에 복사 
...

//tid2 기계어 슈도 코드 
...
register2 = sum; 
register2 = register+1;
sum = register2; 
...

위의 코드를 해석해보자.

  1. 공유 메모리 영역 변수 sum에서 값을 읽어와서 레지스터에 복사
  2. 각자 ++ -- 연산을 수행

여기서 문제가 발생하게 된다. 예를 들어, sum의 값이 1이라고 가정해보자.

  1. sum = 1;
  2. tid1에서 sum에 접근하여 연산 시작 (sum = 1).
  3. register1에 sum 값 복사 (register1 = 1)
  4. tid1의 register1에서 +1 연산 수행 (register1 = 2).
  5. cpu가 tid1 보고 너 time-sharing을 위한 할당시간 다 썼다고 tid2로 context switch 시전 (context switch to tid2)
  6. tid2에서 sum에 접근하여 연산 시작 (sum = 1)
  7. register2에 sum 값 복사 (register2 = 1)
  8. tid2의 register2에서 -1 연산 수행 (register2 = 0)
  9. sum에 register2에 저장된 값을 복사 (sum = 0)
  10. cpu가 tid2 보고 너 time-sharing을 위한 할당시간 다 썼다고 tid1으로 context switch 시전(context switch to tid1)
  11. tid1의 이전 실행 시점에서 PC + 1 (sum = register1)
  12. sum에 register1에 저장된 값을 복사 (sum = 2)

맨 위 예제 c코드를 보면, sum이 1인 초기조건에서 sum을 ++연산 해주는 tid1과 --해주는 tid2가 각각 실행 되었다. 따라서 sum의 값이 항상 1이 돼야 할 것 같은데, 위의 기계어 슈도코드의 실행을 따라가보니 마지막으로 sum에 복사된 값은 2가 되는 경우의 수가 나온다.

즉, 각 스레드가 어떻게 실행 되는지 실행 순서에 따라 결과값인 sum이 바뀌게 된다. 이렇게 여러 프로세스/스레드의 접근 순서나 타이밍에 의해 결과값에 무작위성이 부여 되는 것을 동시성 문제라고 한다.


왜 저런 동시성 문제가 발생할까?

단순히 c언어 예제 코드만 보면 run1, run2가 독립적으로 실행되니까 값도 각 스레드의 독립적인 실행 결과가 반영 되어야 할 것 같아 보인다.

하지만 sum이라는 전역 변수가 존재 해서, 이 두 스레드가 언제 접근해서 언제 연산결과를 반영 하느냐에 따라, 즉 프로그램 런타임 시 기계어의 실행 순서가 어떻게 동작 하느냐에 따라 서로 다른 실행결과를 만들어 내게 된다.

코드를 직관적, 정적으로 바라보지 않고, 코드가 실행되는 런타임 환경, 동적으로 컴퓨터가 어떻게 동작하는지 알면 왜 이런 동시성 문제가 생기는지 알 수 있다.


Race Condition, 동시성 문제가 발생하는 상황

공동 영역 변수, 메모리, 파일 등에 여러 프로세스/스레드가 동시에 접근하여, 그 순서에 따라 무작위한 실행결과가 나오는 상황을 race condition이라고 한다.

Critical Section, 동시성 문제가 발생하는 코드영역

위의 동시성 문제는 특정 코드 영역에서 공유자원에 동시적으로 접근하기 때문에 발생한다. 일반적으로 아래와 같은 구조를 가지게 된다.

while(true){
	//entry section 
    //#### CRITICAL SECTION CODE ####
    //exit section 
    	
    //rest of the code
}

여러 프로세스/스레드에서 공유 영역에 동시에 접근하지 않으려면 위의 critical section을 동시에 실행하지 않도록 막아 줘야한다. 즉, 각 프로세스/스레드 사이에서 일종의 프로토콜을 정의 해서, 어떤 프로세스/스레드가 critical section 영역 코드를 실행하는 동안 다른 프로세스/스레드는 해당 영역을 실행하지 못하도록 알려줘야 한다.

여러 프로세스/스레드가 critical section을 동시에 실행하지 못하도록 해야 하는 프로세스/스레드 간 프로토콜 설계 문제. 이런 문제를 critical section problem(csp)이라고 한다.

그리고 이 critical section 문제에 대한 해는 아래 세 가지 조건을 만족해야 한다.

Critical section 문제 해의 3가지 조건

1. Mutual Exclusion : pi가 임계영역을 실행하는 동안 다른 프로세스들은 해당 영역을 실행하면 안 된다.
2. Progress (avoid dead lock) : 어떤 프로세스도 임계영역을 실행하지 않고 있을 경우, 한 프로세스가 임계영역 실행을 원한다면 그 프로세스는 임계영역에 들어갈 수 있어야 한다
3. Bound waiting (avoid starvation) : 임계영역 진입 횟수에 bound를 두어야 한다. 특정 프로세스만 계속 임계영역에 진입하면, 다른 프로세스들은 계속 임계영역 진입을 기다려야 한다(starvation).

그렇다면 이렇게 동적으로 무작위성이 부여된 코드를 정적이고 예측 가능한 코드로 바꿀 수 있을까? 바로 프로세스/스레드 간 동기화(Synchronization)을 통해 해결할 수 있다.

profile
방구석 언어기술자

0개의 댓글

관련 채용 정보