📀 운영체제
13. 뮤텍스와 세마포어: Chapter 6. Synchronization Tools (Part 3)
락꿈사
2022. 6. 13. 16:19
6.5 뮤텍스 락Mutex Locks
- 임계구역에 대한 하드웨어 기반 해결책은 복잡하며 응용 프로그래머는 사용할 수 없음
- 대신 운영체제 설계자들은 임계구역 문제를 해결하기 위한 상위 수준 소프트웨어 도구들을 개발함
- 뮤텍스락 mutex lock
- 세마포어semaphore
- 모니터monitor
- 뮤텍스 락
- 가장 가장 간단한 형태의 소프트웨어 동기화 도구
- mutual exclutison의 축약어
- 구현
- 프로세스가 임계구역에 들어가기 전에 반드시 락을 획득해야 하고, 임계구역을 빠져나올 때 락을 반환해야 함
- acquire() 함수를 통해 락을 획득
- release() 함수를 통해 락을 반환
- acquire()과 release() 함수 호출은 원자적atomic으로 수행되어야 함
- compare and swap을 사용하여 구현할 수 있음
- 프로세스가 임계구역에 들어가기 전에 반드시 락을 획득해야 하고, 임계구역을 빠져나올 때 락을 반환해야 함
axquire() {
while(!available)
; /* busy wait */
availabe = false;
}
release() {
availalbe = True;
}
while (true){
acquire lock
cirtical section
release lock
remainder section
}
- 코드
#include <stdio.h>
#include <pthread.h>
int sum = 0;
pthread_mutex_t mutex;
void * counter(void *param)
{
int k;
for(k = 0; k<10000; k++){
// entry section
// 락 획득
pthread_mutex_lock(&mutex);
// critical section
sum++;
// exit section
// 락 반납
pthread_mutex_unlock(&mutex);
}
pthread_exit(0);
}
int main(int argc, char const *argv[])
{
pthread_t tid1, tid2;
pthread_mutex_init(&mutex, NULL);
pthread_create(&tid1, NULL, counter, NULL);
pthread_create(&tid2, NULL, counter, NULL);
pthread_join(tid1, NULL);
pthread_join(tid2, NULL);
printf("sum = %d\n", sum);
}
- 위 구현 방식의 단점은 바쁜 대기busy waiting를 해야 한다는 점이 있음
- 프로세스가 임계구역에 있는 동안 임계구역에 들어가기를 원하는 다른 프로세스들은 acquire()함수를 호출하는 반복문을 계속 실행해야 함
- 이러한 점은 하나의 CPU 코어가 여러 프로세스에서 공유되는 실제 다중 프로그래밍 시스템에서 다른 프로세스가 생산적으로 사용할 수 있는 CPU 주기를 낭비한다는 문제점이 있음
- 스핀락spinlock
- 바쁜 대기를 하는 락의 유형을 일컫는 단어
- 락을 사용할 수 있을 때 까지 프로세스가 회전하기 때문에 이렇게 부름
- 문맥 교환에 상당한 시간이 소요될 때 문맥 교환을 하지 않는다는 장점이 있음
- 다중 코어 시스템의 특정 상황에서 실제로 락이 필요할 때 스핀락이 선호됨
- 잠깐 동안 락을 유지해야 하는 경우 다른 스레드가 하나의 코어에서 임계구역을 실행하는 동안 스레드는 다른 처리 코어에서 스핀하고 있을 수 있음
- deadlock 문제는 해결하지 못함
6.6 세마포semaphore
- 뮤텍스와 유사하게 동작하지만 프로세스들을 더 정교하게 동기화 할 수 있는 도구
- 구현
- 세마포S
- 정수 변수
- 초기화를 제외하고는 wait()과 signal()로만 접근할 수 있음
- S = 0 이면 모든 자원이 사용중이라는 것을 나타냄
- wait()
- S값을 검사
- S값을 변경(--)해줌
- S값이 0보다 커질 때 까지 봉쇄됨
- signal()
- S값을 검사
- S값을 변경(++)해줌
- wait()와 signal() 연산 시 세마포어 정수 값을 변경하는 연산은 반드시 원자적atomic으로 수행되어야 함
- 세마포S
wait(S){
while (S <= 0){
; // busy wait
S--;
}
signal(S){
S++;
}
세마포 사용법
- 카운팅 세마포counting semaphore
- 유한한 개수를 가진 자원에 대한 접근을 제어하는데 사용됨
- 세마포S 변수는 가용한 공유 자원의 개수로 초기화 함
- 코드
#include <stdio.h>
#include <pthread.h>
#include <semaphore.h>
int sum = 0;
sem_t sem;
void * counter(void *param)
{
int k;
for(k = 0; k<100000; k++){
// entry section
sem_wait(&sem);
// critical section
sum++;
// exit section
sem_post(&sem);
}
pthread_exit(0);
}
int main(int argc, char const *argv[])
{
pthread_t tid[5]; int i;
// 마지막 파라미터는 공유 자원의 인스턴스 수를 의미
// 5개의 인스턴스로 초기화. --> race condition 문제가 그대로 발생하므로 올바른 값이 나오지 않음
// 1개의 인스턴스로 초기화. --> mutex와 동일하므로 한번에 한 프로세스가 동작. 올바른 값이 나옴
sem_init(&sem, 0, 5);
for (i = 0; i < 5; i++)
pthread_create(&tid[i], NULL, counter, NULL);
for (i = 0; i < 5; i++)
pthread_join(tid[i], NULL);
printf("sum = %d\n", sum);
}
- 이진 세마포어binary semaphore
- mutex락과 유사하게 동작
- 세마포S 변수는 0과 1 사이의 값만 가능
- 코드
#include <stdio.h>
#include <pthread.h>
#include <semaphore.h>
int sum = 0;
sem_t sem;
void * counter(void *param)
{
int k;
for(k = 0; k<10000; k++){
// entry section
sem_wait(&sem);
// critical section
sum++;
// exit section
sem_post(&sem);
}
pthread_exit(0);
}
int main(int argc, char const *argv[])
{
pthread_t tid1, tid2;
// 마지막 파라미터는 공유 자원의 인스턴스 수를 의미
// 현재는 1개의 인스턴스로 초기화.
sem_init(&sem, 0, 1);
pthread_create(&tid1, NULL, counter, NULL);
pthread_create(&tid2, NULL, counter, NULL);
pthread_join(tid1, NULL);
pthread_join(tid2, NULL);
printf("sum = %d\n", sum);
}
- 다양한 동기화 문제를 해결하기 위해 세마포를 사용할 수 있음
- 예시
- P1은 S1 명령문을 수행
- P2는 S2명령문을 수행
- 이 때 S2는 S1이 끝난 뒤에만 수행되어야 한다고 가정
- P1, P2는 세마포 synch를 공유
- synch는 0으로 초기화
- synch 값은 0으로 초기화되어 있으므로 P2가 P2를 수행하는 것은 P1이 signal(synch)를 호출한 후에만 가능함
- 즉 S1 -> S2 의 순서로 실행됨
// P1
S1;
signal(synch);
// P2
wait(synch);
S2;
세마포 구현
- 세마포의 wait(), signal() 또한 바쁜 대기를 함
- 문제 해결 방안
- 프로세스가 wait()연산을 실행하고 세마포 값이 양수가 아닌 것을 발견하면
- 프로세스는 자신을 일시 중지 시킴
- 일지 중지 연산
- 프로세스를 세마포에 연관된 대기 큐에 넣고
- 프로세스의 상태를 대기 상태로 전환
- 일지 중지 연산
- 그 후 제어가 CPU 스케줄러로 넘어감
- 스케줄러는 다른 프로세스를 실행하기 위해 선택함
- 일시 중지된 프로세스는 다른 프로세스가 signal()연산을 실행하면 wakeup() 연산에 의해 재시작 됨
- wakeup() 연산
- 프로세스의 상태를 대기 상태에서 준비 완료 상태로 변경
- 프로세스를 준비 완료 큐로 넣음
- wakeup() 연산
- semaphore 정의
- wait() 연산 정의
- signal() 연산 정의