주의깊게 봐야할 부분은 buffer를 활용하는 부분인데, 이 코드에서 buffer n개를 모두 사용하지 못한다. cell의 n-1개만 사용한다는 뜻인데, cell 하나는 채울 수가 없다. 이유는 index의 in과 out이 같은 것을 empty로 간주하기 때문에 마지막까지 사용하면 full인지 empty인지 구분할 수가 없게 된다.
→ in +1== out 일 때 full이라고 생각하기 때문에
( 컴파일할 때, n을 작게 하면 한 cell이 채워지지 않는다는 것을 알 수 있다. )
n개의 cell을 다 채울려면 어떻게 해야할까? in이나 out으로 full이나 empty를 판단하는 것이 아니라, 버퍼 안의 데이터의 개수를 셈으로써 full이냐 empty이냐를 알도록 하면 된다. 그렇게 하기 위해서는 변수가 필요하다. (아래에서는 공유변수 counter를 사용한다.)
개수를 다루는 변수로 counter를 사용하는데, counter는 전역변수이면서 공유변수 이여야 하는데 쓰레드의 counter 공유 변수 동시 접근 시 문제가 생긴다.
그 문제가 바로 Race Condition이다.
Race Condition
우리가 생각할 때, counter=counter+1이라는 코드가 실행되면 한 번에 딱 실행될거원라고 생각하지만 실제로는 아니다.
이 코드가 기계어로 실행될 때는, 3가지 과정을 거치는데 register에 저장하고 register에서 값을 증가하고 counter에 다시 register에 넣는 단계를 거친다.
counter=counter-1의 경우도 마찬가지이다. 따라서, 공유변수 counter를 두고, 2개의 thread에서 counter값에 접근하면, counter값이 이상하게 변할 수 있다. 위에서 보면 실행 상은 counter가 5여야 하는데, 실제로 기계어로 실행되면 4또는 6이 될 수도 있는 것이다.
이 문제가 Race condtition이다.Race 즉, 서로 다른 쓰레드가 같은 자원을 두고 경쟁하는 상태를 의미한다.
그렇다면 이런 Race condition은 어떠한 방법으로 해결할까? 지금 문제가 되는 것이 기계어가 3줄이니까 3줄이 한번에 돌지 않고 문맥 교환이 일어나서 상대 쓰레드가 들어와서 공유변수의 값을 수정해서 벌어지는 일들이다. 그럼 문맥교환이 일어나더라도 간섭이 없도록 코드가 실행되게 만들면 어떨까?
이 방법이 바로 Atomic한 실행이라고 한다. 예를 들어 counter부분을 임계구역으로 만들어놓으면 생산자에서 코드 2줄까지 실행하고 문맥교환이 일어나서 소비자에서 진입하려고 할 때, 진입하지 못하도록 하고 대기하도록 한다.
원소적(Atomic) 실행
: 공유변수를 통해 상호작용하는 프로세스 혹은 쓰레드 간에 문맥교환이 언제 일어나도 간섭이 없는 실행이 보장되는 것.
-> 공유변수를 사용하는 코드 영역에 임계 구역을 설정한다. 아까 코드에서는 register와 counter를 사용하여서 counter의 값을 증가시키거나 감소시키는 부분을 임계 구역으로 설정하는 것이다.
한 쓰레드가 먼저 임계구역 내에 진입하여 실행 중 문맥 교환이 발생하여 상대 쓰레드에 선점되더라도 그 쓰레드가 임계 구역에 진입하는 것을 허락하지 않고, 대기하도록 한다. 즉, 다른 쓰레드가 실행되더라도 임계구역에는 못들어간다는 의미이다.
임계구역(Critical Section) 설정
임계구역이란 Atomic 실행을 위하여 각 프로세스 혹은 쓰래드가 공유변수, 자료구조, 파일 등을 배타적으로 읽고 쓸 수 있도록 설정한 코드 세그먼트이다.
임계구역 설정하는 것이 비간섭으로 돌게 하는 수단이 된다. (임계 구역의 정의 중요)
→ 공유 변수가 임계 구역처럼 느껴질 수 있겠지만 변수는 수행되는 부분이 아니다. 수행은 코드가 하기 때문에 임계구역은 코드에 만들어 놓는다. 배타적으로 돌도록 코드에 설정해준다.
entry, exit을 설정해 놓음으로써 이 부분은 배타적으로 돌아야한다고 명시한다. enter, exit으로 임계구역을 만들어 놓아야 한다. 화장실을 예시로 든다면, 화장실에 들어갈 때 문을 잠그로, 나갈 때 여는 것과 유사하다.
임계구역을 하면 한쪽이 먼저 들어가게 되겠고, 그렇게 하면 경쟁하는 다른쪽들은 못 들어가고 있다가 먼저 들어간 쪽이 빠져나가면 그제서야 다른 쪽들 중에 하나가 들어가게 됨으로써 진입과 진출이 순서화된다. (Serialize)
이런 것을 동기화라고 한다. 즉, 코드 상으로는 독립적이지만 실행 상황에서 선행제약을 만드는 것이다.
임계 구역 설정은 mutex가 기본이다. lock을 mutex 변수로 선언한다.
위의 코드에서 보면 pthread_mutex_lock으로 잠금을 걸고, 만약 버퍼 크기가 꽉찼으면 pthread_cond_wait으로 대기한다. 그리고 그게 아니라면 in으로 넣고 count값을 증가시켜준 뒤, pthread_cond_signal 함수를 호출한다. 이 함수는 대기 중인 쓰레드에게 signal을 보내 pthread_cond_wait으로 대기중인 쓰레드를 깨우게 되어 다른 쓰레드가 이후의 작업을 진행할 수 있도록 해준다.
'CS > 운영체제' 카테고리의 다른 글
[5] 프로세스 (0) | 2022.03.24 |
---|---|
[4] 컴퓨터 구조와 OS 연계 (0) | 2022.03.21 |
[3] 컴퓨터 구조와 OS 연계 (0) | 2022.03.14 |
[2] 시분할 시스템, 실시간 시스템 (0) | 2022.03.09 |
[1] 운영체제의 발전 (0) | 2022.03.07 |