Notice
Recent Posts
Recent Comments
Link
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | ||
6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 | 14 | 15 | 16 | 17 | 18 | 19 |
20 | 21 | 22 | 23 | 24 | 25 | 26 |
27 | 28 | 29 | 30 |
Tags
- 냅색알고리즘
- KT포트포워딩
- 가운데를말해요
- 이것이자바다확인문제
- 컴퓨터비전
- 확인문제
- java
- 코테
- 카카오코테
- 웹개발기초
- 이것이자바다
- 백준9012
- 백준온라인저지
- 합성곱연산
- BOJ
- 백준10828
- 스파르타코딩클럽
- BOJ1655
- 딥러닝
- 백준
- 윤곽선검출
- 백준평범한배낭
- 백준괄호
- 백준가운데를말해요
- 2019카카오코테
- 코딩테스트실력진단
- 백준스택
- 운영체제
- 코드트리
- 이것이자바다9장
Archives
- Today
- Total
코딩하는 락커
16. 철학자들은 왜 굶어 죽었을까?: Chapter 7. Synchronization Examples (Part 2 ~ Part 3) 본문
📀 운영체제
16. 철학자들은 왜 굶어 죽었을까?: Chapter 7. Synchronization Examples (Part 2 ~ Part 3)
락꿈사 2022. 6. 20. 17:09식사하는 철학자들 문제

- 식사하는 철학자 문제 가정
- 5명의 철학자가 5개의 의자에 각각 앉아 있음
- 테이블 중앙에는 밥이 있음
- 테이블에는 다섯개의 젓가락이 놓여 있음
- 철학자들은 배가 고파지면 자신에게 가장 가까이 있는 두 개의 젓가락(왼쪽 젓가락과 오른쪽 젓가락)을 집으려고 시도함
- 철학자들은 한번에 한 젓가락만 집을 수 있음
- 철학자들은 이미 옆 사람의 손에 들어간 젓가락을 집을 수 없음
- 동시에 두 개의 젓가락을 집으면 젓가락을 놓지 않고 식사를 함
- 식사를 마치면 두 개의 젓가락을 모두 놓고 다시 생각을 시작함
- 식사하는 철학자 문제는 교착 상태Deadlock과 기아Starvation을 발생시키지 않고 여러 스레드에게 여러 자원을 할당해야 할 필요를 단순하게 표현한 것
세마포 해결안Semaphore Solution

- 각 젓가락을 하나의 이진 세마포로 표현함

- 철학자는 그 세마포에 wait() 연산을 실행하여 젓가락을 집으려고 시도함
- 또한 해당 세마포에 signal() 연산을 실행하여 자신의 젓가락을 놓음
- 이 해결안은 인접한 두 철학자가 동시에 식사하지 않는다는 mutual exclusion 조건을 보장하지만, deadlock을 야기할 가능성이 있음
- 5명의 철학자가 동시에 배가 고프게 되어 각각 자신의 왼쪽 젓가락을 잡는다고 가정
- chopstick의 모든 원소들은 0이 됨
- 각 철학자는 그들의 오른쪽 젓가락을 집으려고 하면 영원히 기다려야 함. 즉 데드락 발생
- 데드락 문제의 여러 가지 해결책
- 최대 4명의 철학자들만이 테이블에 동시에 앉을 수 있도록 함
- 한 철학자가 젓가락 두개를 모두 집을 수 있을 때만 젓가락을 집도록 허용함
- 비대칭 해결안 사용
- 홀수 번호의 철학자는 왼쪽 젓가락을 집은 다음 오른쪽 젓가락을 집도록 함
- 짝수 번호의 철학자는 오른쪽 젓가락을 집은 다음 왼쪽 젓가락을 집도록 함
- 그러나 이러한 데드락 문제의 해결책이 반드시 기아의 가능성도 제거 하는 것은 아님
모니터 해결안Monitor Solution


- 철학자들은 양쪽 젓가락 모두 얻을 수 있을 때만 젓가락을 집을 수 있다는 제한을 강제함
- enum 자료구조 State 통해 철학자가 처할 수 있는 세가지 상태를 구분함
- condition 변수 Self를 사용하여 철학자 i가 배고프지만 자신이 원하는 젓가락을 집을 수 없을 때 젓가락 집기를 미룰 수 있도록 함
- 젓가락은 분배는 모니터 DiningPhilosophers에 의해 제어됨
- 각 철학자는 식사하기 전에 pickup() 연산을 반드시 호출하고, 이 연산이 성공적으로 끝나면 철학자는 식사할 수 있음
- 각 철학자는 식사를 마친 후에 putdown() 연산을 반드시 호출해야 함
- 이 해결안은 이웃한 두 철학자가 동시에 식사하지 않는다는 것과 데드락이 발생하지 않는다는 것을 보장함
- 그러나 철학자가 굶어죽는 것, 즉 기아 상태가 발생 가능하다는 것에 유의해야 함
- 실습 코드(C)
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>
#define true 1
#define NUM_PHILS 500
enum {THINKING, HUNGRY, EATING} state[NUM_PHILS];
pthread_mutex_t mutex_lock;
// condition variable
// 이 변수에 접근할 때는 항상 동기화가 됨
pthread_cond_t cond_vars[NUM_PHILS];
void init(){
int i;
for (i=0; i<NUM_PHILS; i++){
state[i] = THINKING;
pthread_cond_init(&cond_vars[i], NULL);
}
pthread_mutex_init(&mutex_lock, NULL);
srand(time(0));
}
int leftOf(int i){
return (i+NUM_PHILS-1) % NUM_PHILS;
}
int rightOf(int i){
return (i+1) % NUM_PHILS;
}
void think(int id){
printf("%d: Now, I'm thingking...\n", id);
usleep((1+rand() % 50)*10000);
}
void eat(int id){
printf("%d: Now, I'm eating...\n", id);
usleep((1+rand()%50)*10000);
}
void test(int i){
if(state[i] == HUNGRY && state[leftOf(i)]!=EATING && state[rightOf(i)] != EATING)
{
state[i] = EATING;
pthread_cond_signal(&cond_vars[i]);
}
}
void pickup(int i){
// mutex lock 획득
// pthread_mutex_lock(&mutex_lock);
// critical section
state[i] = HUNGRY;
test(i);
while(state[i] != EATING){
pthread_cond_wait(&cond_vars[i], &mutex_lock);
}
// exit section
// pthread_mutex_unlock(&mutex_lock);
}
void putdown(int i){
// mutex lock 획득
// pthread_mutex_lock(&mutex_lock);
// critical section
state[i] = THINKING;
test(leftOf(i));
test(rightOf(i));
// exit section
// pthread_mutex_unlock(&mutex_lock);
}
void *philosopher(void *param){
// 받아온 idx 주소의 값을 id에 넣어줌
int id = *((int *)param);
while (true){
think(id);
pickup(id);
eat(id);
putdown(id);
}
}
int main(int argc, char const *argv[])
{
int i;
pthread_t tid;
// mutex lock, conditin variables, 철학자 상태 초기화
init();
for (i=0; i<NUM_PHILS; i++){
// philisopher 스레드 5개 생성
// pid를 (void *)로 타입캐스팅 한 idx의 주소를 넘겨줌
pthread_create(&tid, NULL, philosopher, (void *)&i);
}
for (i=0; i<NUM_PHILS; i++)
pthread_join(tid, NULL);
return 0;
}


- 실습 코드(JAVA)
package com.os_study;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
// 재진입 가능 (cpu에서 context switch가 발생했을 때 재진입을 가능하게 해주는 패키지)
import java.util.concurrent.locks.ReentrantLock;
enum State{
THINKING, HUNGRY, EATING
}
public class DiningPhilosophers {
public static void main(String[] args) {
int numOfPhils = 5;
Philosopher[] philosophers = new Philosopher[numOfPhils];
DiningPhilosopherMonitor monitor = new DiningPhilosopherMonitor(numOfPhils);
for (int i=0; i<philosophers.length; i++){
new Thread(new Philosopher(i, monitor)).start();
}
}
private static class Philosopher implements Runnable{
private int id;
private DiningPhilosopherMonitor monitor;
public Philosopher(int id, DiningPhilosopherMonitor monitor){
this.id = id;
this.monitor = monitor;
}
@Override
public void run() {
while (true){
think();
monitor.pickup(id);
eat();
monitor.putdown(id);
}
}
private void think() {
try {
System.out.println(id + ": Now I'm thinking...");
Thread.sleep((long)(Math.random()*500));
} catch (InterruptedException e) {}
}
private void eat() {
try {
System.out.println(id + ": Now I'm eating...");
Thread.sleep((long)(Math.random()*50));
} catch (InterruptedException e) {}
}
}
private static class DiningPhilosopherMonitor {
private int numOfPhils;
private State[] state;
private Condition[] self;
private Lock lock;
public DiningPhilosopherMonitor(int num) {
numOfPhils = num;
state = new State[num];
self = new Condition[num];
lock = new ReentrantLock();
for (int i=0; i<num; i++){
state[i] = State.THINKING;
self[i] = lock.newCondition();
}
}
private int leftOf(int i){
return (i + numOfPhils - 1) % numOfPhils;
}
private int rightOf(int i){
return (i + 1) % numOfPhils;
}
private void test(int i){
if (state[i] == State.HUNGRY &&
state[leftOf(i)] != State.EATING &&
state[rightOf(i)] != State.EATING)
{
state[i] = State.EATING;
self[i].signal();
}
}
public void pickup(int id) {
lock.lock();
try {
state[id] = State.HUNGRY;
test(id);
if (state[id] != State.EATING)
self[id].await();
} catch (InterruptedException e){
}
finally {
lock.unlock();
}
}
public void putdown(int id) {
lock.lock();
try {
state[id] = State.THINKING;
test(leftOf(id));
test((rightOf(id)));
} finally {
lock.unlock();
}
}
}
}

7.5 대체 방안들Alternative Approaches
- 다중 코어 시스템의 등장과 함께 이러한 여러 처리 코어의 이점을 극대화할 수 있는 병행 어플리케이션 개발에 관한 압력이 증가하게 됨
- 그러나 병행 어플리케이션은 race condition, deadlock과 같은 라이브니스 위험을 증가시킴
- 전통적으로 mutex lock, 세마포, 모니터와 같은 기법이 이러한 쟁점을 해결하기 위해 사용되어 왔음
- 그러나 처리 코어의 개수가 증가할수록 race condition과 deadlock위험이 없는 다중 스레드 어플리케이션을 설계하는 작업은 점점 더 어려워지고 있음
- 스레드-세이프Thread-Safe 어플리케이션 설계에 도움을 줄 수 있는 프로그래밍 언어와 하드웨어의 다양한 기능
- 트랜잭션 메모리
- 데이터베이스 이론 분야에서 출발한 아이디어로 프로세스 동기화 전략을 제공함
- 메모리 트랜잭션: 메모리 일기와 쓰기 연산의 원자적인 연속적 순서
- 한 트랜잭션의 모든 연산이 완수되면 메모리 트랜잭션은 확정commit됨
- 그렇지 않으면 그 시점까지 완수된 모든 연산은 취소되고 트랜잭션 시작 이전의 상태로 되돌려야roll-back함
- 트랜잭션 메모리는 소프트웨어 또는 하드웨어로 구현될 수 있음
- 소프트웨어 트랜잭션 메모리STM
- 특별한 하드웨어 없이 소프트웨어 만으로 구현됨
- 하드웨어 트랜잭션 메모리HTM
- 하드웨어 캐시 계층 구조와 캐시 일관성 프로토콜 사용
- 소프트웨어 트랜잭션 메모리STM
- OpenMP
- 컴파일러 디렉티브와 API로 구성됨
- ex) #pragma omp parallel
- 스레드의 생성과 관리가 OpenMP 라이브러리에 의해서 처리되어 응용 개발자들은 신경 쓰지 않아도 됨
- 함수형 프로그래밍
- 함수형 프로그래밍은 명령형 언어가 제공하는 패러다임과 많이 다른 패러다임을 따름
- 근본적인 차이는 함수형 언어는 상태를 유지하지 않는다는 것을 들 수 있음
- 함수형 언어는 변경 가능 상태를 허용하지 않기 때문에 race condition이나 deadlock과 같은 쟁점에 대해 신경 쓸 필요가 없음
- 트랜잭션 메모리
'📀 운영체제' 카테고리의 다른 글
18. 데드락과 뱅커 알고리즘: Chapter 8. Deadlocks (Part 2) (0) | 2022.06.21 |
---|---|
17. 데드락의 이해: Chapter 8. Deadlocks (Part 1) (0) | 2022.06.20 |
15. 동시성 제어의 고전적 문제들: Chapter 7. Synchronization Examples (Part 1) (0) | 2022.06.14 |
14. 모니터와 자바 동기화: Chapter 6. Synchronization Tools (Part 4) (0) | 2022.06.13 |
13. 뮤텍스와 세마포어: Chapter 6. Synchronization Tools (Part 3) (0) | 2022.06.13 |