프로세스 동기화
- 프로세스들 사이의 수행시기를 맞추는 것
- 동기화 방법
- 실행 순서 제어
- 프로세스를 올바른 순서대로 실행하기
- 상호 배제
- 동시에 접근해서는 안 되는 자원에 하나의 프로세스만 접근하게 하기
- 실행 순서 제어를 위한 동기화
- 동시에 실행되는 프로세스를 올바른 순서대로 실행하는 것
- 예시)
- Read 프로세스, Write 프로세스와 같이 읽고 쓰기 순서가 중요한 프로세스는 순서 동기화가 잘 되어야함
- Read 프로세스는 특정 위치에 값이 존재한다는 조건 하에 실행을 이어나갈 수 있으므로 동시에 실행되는 Write 프로세스의 순서가 잘 제어되야 함
- 상호 배제
- 공유가 불가능한 자원의 동시 사용을 피하기 위해 사용하는 알고리즘
- 동시에 접근해서는 안되는 자원에 동시에 접근하지 못하게 막음
- 은행 계좌 문제
- 생산자와 소비자 문제
#include <stdio.h>
#include <pthread.h>
void *produce(void *arg);
void *consume(void *arg);
int sum = 0;
int main() {
printf("초기 합계: %d\n", sum);
pthread_t producer, consumer;
pthread_create(&producer, NULL, produce, NULL);
pthread_create(&consumer, NULL, consume, NULL);
pthread_join(producer, NULL);
pthread_join(consumer, NULL);
printf("producer, consumer 스레드 실행 이후 합계: %d\n", sum);
return 0;
}
void *produce(void *arg) {
for(int i = 0; i < 100000; i++) {
sum++;
}
return NULL;
}
void *consume(void *arg) {
for(int i = 0; i < 100000; i++) {
sum--;
}
return NULL;
}
- 생산자와 소비자는 'sum'이라는 변수를 공유
- 생산자는 sum에 1을 더하고, 소비자는 sum에 1을 뺌
- 실제 실행 시에 예상치 못한 값을 얻게 되는데, 이는 생산자와 소비자 프로세스가 동기화되지 않아 동시에 접근해서는 안되는 자원에 접근했기에 생기는 문제
- 공유 자원
- 위 코드의 sum과 같은 공동의 자원을 공유 자원이라고 함
- 임계 구역
- 동시에 접근하면 문제가 발생하는 자원에 접근하는 "코드 영역"
- 경쟁 상태 ( race condition )
- 잘못된 실행으로 인해 여러 프로세스가 동시 다발적으로 임계 구역의 코드를 실행하여 문제가 발생하는 경우
- 위의 계좌 잔액 문제, 생산자-소비자 문제는 레이스 컨디션의 사례
- 임계 구역 문제를 해결을 위한 원칙
- 상호 배제
- 한 프로세스가 임계 구역에 진입했다면 임계 구역에 진입하고자 하는 프로세스는 들어올 수 없다
- 진행
- 임계 구역에 어떤 프로세스도 진입하지 않았다면 임계 구역에 진입하고자 하는 프로세스는 들어갈 수 있어야한다
- 유한 대기
- 한 프로세스가 임계 구역에 진입하고 싶다면 그 프로세스는 언제가는 임계 구역에 들어올 수 있어야한다
뮤텍스 락
- 동시에 접근해서는 안 되는 자원에 동시에 접근하지 않도록 만드는 도구
- 상호 배제를 위한 동기화 도구
//mutexlock
#include <stdio.h>
#include <pthread.h>
void *produce(void *arg);
void *consume(void *arg);
int sum = 0;
pthread_mutex_t mutex;
int main()
{
pthread_mutex_init (&mutex, NULL);
printf ("초기 합계: %d\n", sum);
pthread_t producer, consumer;
pthread_create (&producer, NULL, produce, NULL);
pthread_create (&consumer, NULL, consume, NULL);
pthread_join (producer, NULL);
pthread_join (consumer, NULL);
printf ("producer, consumer 스레드 실행 이후 합계: %d\n", sum);
return 0;
}
void *produce(void *arg)
{
for (int i = 0; i < 100000; i++)
{
pthread_mutex_lock (&mutex);
sum++;
pthread_mutex_unlock (&mutex);
}
return NULL;
}
void *consume(void *arg)
{
for (int i = 0; i < 100000; i++)
{
pthread_mutex_lock (&mutex);
sum--;
pthread_mutex_unlock (&mutex);
}
return NULL;
}
- aquire()
- pthread_mutex_lock (&mutex);
- 임계 구역에 진입하기 전에 호출하는 함수
- 임계 구역이 잠겨 있다면 임계 구역이 열릴 때까지 임계 구역을 반복적으로 확인하고, 임계 구역이 열려 있다면 임계 구역을 잠그는 함수
- release()
- pthread_mutex_unlock (&mutex);
- 임계 구역에서의 작업이 끝나고 호출하는 함수
- lock이 잠겨 있는지 쉴 새 없이 반복하며 확인해 봄
- 이러한 대기 방식을 바쁜 대기라 부름
세마포
- 뮤텍스 락과 비슷하지만, 조금 더 일반화된 방식의 동기화 도구
- 뮤텍스 락은 하나의 공유 자원에 접근하는 프로세스를 상정한 방식
- 공유 자원이 여러 개 있을 경우 여러 개의 프로세스가 각각 공유 자원에 접근이 가능해야 한다는 필요에서 나온 것이 세마포
//semaphore
#include <stdio.h>
#include <pthread.h>
#include <semaphore.h>
void *produce(void *arg);
void *consume(void *arg);
int sum = 0;
sem_t sem;
int main() {
sem_init(&sem, 0, 1);
printf("초기 합계: %d\n", sum);
pthread_t producer, consumer;
pthread_create(&producer, NULL, produce, NULL);
pthread_create(&consumer, NULL, consume, NULL);
pthread_join(producer, NULL);
pthread_join(consumer, NULL);
printf("producer, consumer 스레드 실행 이후 합계: %d\n", sum);
return 0;
}
void *produce(void *arg) {
for(int i = 0; i < 100000; i++) {
sem_wait(&sem);
sum++;
sem_post(&sem);
}
return NULL;
}
void *consume(void *arg) {
for(int i = 0; i < 100000; i++) {
sem_wait(&sem);
sum--;
sem_post(&sem);
}
return NULL;
}
- MacOS에서는 일반 sem관련 함수 지원 x
//semaphore_MacOS
#include <stdio.h>
#include <pthread.h>
#include <dispatch/dispatch.h>
void *produce(void *arg);
void *consume(void *arg);
int sum = 0;
dispatch_semaphore_t sem;
int main() {
sem = dispatch_semaphore_create(1);
printf("초기 합계: %d\n", sum);
pthread_t producer, consumer;
pthread_create(&producer, NULL, produce, NULL);
pthread_create(&consumer, NULL, consume, NULL);
pthread_join(producer, NULL);
pthread_join(consumer, NULL);
printf("producer, consumer 스레드 실행 이후 합계: %d\n", sum);
return 0;
}
void *produce(void *arg) {
for(int i = 0; i < 100000; i++) {
dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);
sum++;
dispatch_semaphore_signal(sem);
}
return NULL;
}
void *consume(void *arg) {
for(int i = 0; i < 100000; i++) {
dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);
sum--;
dispatch_semaphore_signal(sem);
}
return NULL;
}
- 전역 변수 sem
- 임계 구역에 진입할 수 있는 프로세스의 개수
- wait 함수
- 임계 구역에 들어가도 좋은지, 기다려야 할지를 알려줌
- signal 함수
- 임계 구역 앞에서 기다리는 프로세스에 들어다고 된다고 신호를 주는 함수
모니터
- 세마포의 단점을 보완하기 위해 모니터 등장
- 세마포는 wiat와 signal을 일일히 명시해야되기 때문에 어려운 점이 존재
- 코드가 방대해지거나 복잡해지면 잘못된 사용이 일어날 확률이 높아짐
- 모니터는 공유 자원과 공유 자원에 접근하기 위한 인터페이스를 묶어 관리
- 프로세스는 반드시 인터페이스를 통해서만 공유 자원에 접근 가능
//monitor.cpp
#include <iostream>
#include <thread>
#include <mutex>
#include <condition_variable>
std::mutex mtx;
std::condition_variable cv;
bool access = true;
int sum = 0;
void produce() {
for(int i = 0; i < 100000; i++) {
std::unique_lock<std::mutex> lck(mtx);
cv.wait(lck, []{return access;});
sum++;
access = false;
cv.notify_one();
}
}
void consume() {
for(int i = 0; i < 100000; i++) {
std::unique_lock<std::mutex> lck(mtx);
cv.wait(lck, []{return !access;});
sum--;
access = true;
cv.notify_one();
}
}
int main() {
std::cout << "초기 합계: " << sum << '\n';
std::thread producer(produce);
std::thread consumer(consume);
producer.join();
consumer.join();
std::cout << "producer, consumer 스레드 실행 이후 합계: " << sum << '\n';
return 0;
}
- 조건 변수 (cv)
- 특정 조건을 바탕으로 프로세스를 실행하고 일시 중단하기 위해 사용
- wait와 signal(norify_one) 연산 수행
- 동작
- 특정 프로세스가 아직 실행될 조건이 되지 않았을 때에는 wait를 통해 실행을 중단
- 특정 프로세스가 실행될 조건이 충족되었을 때에는 signal을 통해 실행을 제어
'CS > OS' 카테고리의 다른 글
가상 메모리 (페이징) [혼공컴구] (0) | 2024.01.31 |
---|---|
메모리 스와핑 [혼공컴구] (0) | 2024.01.31 |
스케줄링 알고리즘 [혼공컴구] (1) | 2023.12.22 |
프로세스와 스레드 [혼공컴운] (0) | 2023.12.15 |
프로세스 계층 구조 & 생성 기법 [혼공컴운] (0) | 2023.12.15 |