본문 바로가기
CS/운영체제

[Chapter 6. 프로세스 동기화] 생산자 소비자 문제로 보는 원소적 실행과 임계 구역

by 베어 그릴스 2022. 9. 6.
320x100
본 정리는 운영체제(Operating System: Concepts) 9th edition과 22학년도 1학기 건국대학교 운영체제 수업을 바탕으로 하고 있습니다.

 

협력적 프로세스(Cooperating Process)가 병행 또는 병렬로 실행될 때

여러 프로세스가 공유하는 데이터의 무결성에 어떤 문제가 일어나는가?

 

* 협력적 프로세스 : 시스템 내에서 실행 주인 다른 프로세스의 실행에 영향을 주거나 받는 프로세스

 


생산자 소비자 스레드를 보자.

 

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

void consumer (void);
char buffer[n];

int n, in = 0, out = 0;

int main ()
{
	char nextp; int i;
	pthread_t tid;
	pthread_create (&tid, NULL, consumer,NULL);
	for (i = 0; i < 500; i++) {
		produce an item in nextp
		while ((in+1) % n == out) ; // 대기 loop
		buffer[in] = nextp;
		in++; in %= n;
	}
	pthread_join (tid);
}

void consumer(void)
{
	char nextc;
	for (i = 0; i < 500; i++) {
		while (in == out) ;
		nextc = buff[out];
		out++; out %= n;
		...
		consume the item in nextc;
}
}

* 생산자 프로세스 : 정보를 생산하는 프로세스

* 소비자 프로세스 : 생산자 프로세스가 생산한 정보를 소비하는 프로세스

 

생산자와 소비자 프로세스들이 병행으로 실행되도록 하기 위해서

공유하는 메모리 영역에 원형 공유 버퍼를 생성, 생산자가 정보를 채워 넣고 소비자가 소모할 수 있도록 만들어주는 것이 해결책이었다.

 

이때 두 포인터 변수 in과 out을 통해 구현할 수 있었는데, (데이터들의 위치를 가리키는 변수들)

이 두 변수는 공유 변수이지만 한쪽에서만 값을 변경하기 때문에 간섭 문제가 발생하지는 않는다.

 

그러나 생산자 측에서 입력 후에 in++를 하며, 소비자 측에선 in==out을 empty로 간주하므로 in<out-1이어야 fullf과 empty를 구분할 수 있어서 버퍼의 크기 n 중 n-1만큼만 사용할 수 있다.

 

버퍼의 크기를 전부 사용하기 위해서는 불가피하게도 공유변수를 사용해서, 생산자 스레드는 생산할 때마다 +1 소비자 스레드는 소비할 때마다 -1 하는 식으로 구현해야만 한다.

 

즉 다음과 같이 될 것이다.

#생산자

while (1) {
...
produce an item in nextp
...
	while ( counter == n) ;
	//wait for an empty buffer
	buffer[in] = nextp;
	in = (in + 1) % n ;
	counter = counter + 1;
}
#소비자

while (1) {
	while (counter == 0) ;
	// wait for an item
	nextc = buffer[out] ;
	out = (out + 1) % n ;
	counter = counter -1;
...
consume the item in nextc
...
}

 

 

그러나 위와 같이 공유변수의 값을 동시에 다른 프로세스에서 접근하여 값을 write 하는 경우 문제가 발생할 수 있다.

 

우리가 보기엔 그저 코드 한줄이지만, 기계어로 변환되어 실질적으로 실행될 때는 다음과 같이 실행된다.

#고급어
counter = counter +1;

#기계어
register1 = counter ;
register1 = register1 + 1 ;
counter = register1 ;

#고급어
counter = counter -1;

#기계어
register2 = counter ;
register2 = register2 - 1 ;
counter = register2 ;

스레드의 context switching이 언제 될지는 우리는 전혀 알 수 없다.

 

현재 counter의 값이 5이고, 양쪽 스레드 둘다 

register1 = register1 + 1 ;

register2 = register2 - 1 ;

까지만 실행되었다고 생각해보자.

 

우리가 예상했던 결과는 양쪽에서 한번 +1 한번 -1했으니 5를 예상하겠지만, 위와 같이 스위칭된 경우엔 

register1 = register1 + 1 ; 에서는 6으로 변수를 초기화할 것이고,

register2 = register2 - 1 ; 에서는 4로 변수를 초기화하여 예상치 못한 오류가 생기게 된다.

 

 

이처럼 동시에 여러 프로세스가 동일한 자료를 접근 & 조작하여 그 실행 결과가 접근이 발생한 특정 순서에 의존하는 상황을

경쟁 상황(race condition)이라고 한다.

 

따라서 한 순간에 하나의 프로세스만이 공유 변수를 조작하도록 보장해야 하며,

이것을 위해 어떤 형태로는 프로세스들이 동기화(Synchronization) 되도록 해야 한다.

 

*동기화 : 코드 상에 임계 구역을 설정하여 진입/진출을 순서화(Serialize) 하는 것

 

원소적(Atomic) 실행

공유 변수를 통해 상호작용하는 프로세스 또는 스레드 간에 문맥 교환이 언제 일어나도 간섭이 없는 실행이 보장되는 것을 말한다.

원소적 실행을 위해서는 공유 변수를 사용하는 코드 영역에 임계 구역을 설정한다.

 

 

임계 구역 (Critical Section)


원소적 실행을 위해 각 프로세스 혹은 쓰래드가 공유 변수, 자료구조, 파일 등을 배타적으로 읽고 쓸 수 있도록 설정한 코드 세그먼트

 

각 프로세스는 임계 구역이라는 코드 부분을 포함하고 있으며,

한 프로세스가 자신의 임계 구역에서 수행하는 동안에 다른 프로세스들은 그들의 임계 구역에 들어갈 수 없다.

 

코드 상에 임계 구역을 설정하여 프로세스들의 진입과 진출을 순서화(Serialize)하는 것을 통해

프로세스들 간에 동기화(Synchronization)가 일어날 수 있는 것이다.

 

임계 구역의 설정에는 mutex를 기본적으로 사용하며, (mutex : mutual exclusion, 상호 배제)

생산자-소비자 문제 코드를 다음과 같이 변경할 수 있다.

 

// 생산자 코드
while (1) {
	...
	produce an item in nextp
	...
	while (counter == n); // wait for an empty buffer
	buffer[in] = nextp;
	in = (in + 1) % n;
    
	Enter_Mutex(lock);
	counter = counter + 1;
	Exit_Mutex(lock);
}

// 소비자 코드
while (1) {
	while (counter == 0); // wait for an item
	nextc = buffer[out];
	out = (out + 1) % n;
    
	Enter_Mutex(lock);
	counter = counter -1;
	Exit_Mutex(lock);
    
	...
	consume the item in nextc
	...
}

 

임계 구역에 대한 요구사항

1. 상호 배제 (mutual exclusion)

한 프로세스가 임계 구역을 실행하는 중일 때 다른 어떤 프로세스도 임계 구역을 실행할 수 없다.

 

2. 진행 (progress)

자신의 임계 구역에서 실행되고 있는 프로세스가 없고 그들 자신의 임계 구역에 진입하려고 하는 프로세스들이 있는 상황이라면

나머지 구역에서 실행 중이지 않은 프로세스들만 다음에 누가 그 임계 구역으로 진입할 수 있는 지를 결정하는 데에 참여할 수 있으며

이 결정은 무한정 연기될 수 없다.

 

3. 제한된 대기 (bounded waiting)

한 프로세스가 자신의 임계 구역에 대한 진입 요청을 한 후부터 그 요청이 허용될 때까지의 기간 내에

다른 프로세스가 그들 자신의 임계구역에 진입하도록 허용되는 횟수에는 제한이 있어야 한다.

728x90