철학자는 왜 양손으로 스파게티를 먹을까?

Updated:

Categories:

Tags:

#

Philosophers

  • 필로소퍼라는 과제는 운영체제에서 스레드에 관해서 공부할때 많이 사용하는 예제중 하나이다.

    실제로도 시중에 42말고도 많은 자료들이 존재하며 레퍼런스 또한 많이 존재한다

#

프로세스와 스레드

  • Process와 Thread의 차이

    • Process는 관리의 단위(연산)이고, 관리의 주체는 OS가 한다.
    • 연산은 보통은 연속적인 작업을 진행하게 된다. 이때 생성이 되는게 흐름이 생기게 되는데 이 흐름을 Thread라고 한다
    • 기본적으로 Process 한개당 Thread는 항상 1개 이상 존재한다
    • 흐름이 만약 N개가 있다면?
    • Thread는 기본적으로 동시성을 가지게 되는데, 각자 연산을 처리하게 된다. 이렇게 되면 곧 Multi-Threading이라고 한다
    • Process에 속한 모든 Thread는 Process의 Virtual Memory공간으로 제약이 된다

      이말은 곧 Process에 속한 모든 Thread는 같은 공간을 사용한다고 볼 수 있다

  • 쉽게 이해 하보자

    • Process 는 집이라고 보면

      Thread 는 집에 살고 있는 사람들인데,

      각자의 방은 사생활을위해서 보호해주지만 공용으로 쓰는 공간 (거실, 부엌, 화장실 등)은 공유하게 된다

#

스레드의 종류

  1. 사용자 레벨 스레드(User-Level Thread)
    • 사용자 스레드는 커널 영역의 상위에서 지원되며 일반적으로 사용자 레벨의 라이브러리를 통해 구현이 된다. 단점으로는 하나의 스레드가 시스템 호출 등으로 중단되면 나머지 모든 스레드 역시 중단이 된다고 한다. 그 이유로는 프로세스도 커널에 메모리를 반납하기 때문이지 않을까? 생각을 한다
  2. 커널 레벨 스레드(Kernel-Level Thread)
    • 커널 스레드는 운영체제가 지원하는 스레드 기능으로 구현되어있다. 커널이 직접 스레드의 생성 및 스케줄링 등을 관리한다. 스레드가 시스템 호출 등으로 중단되더라도, 커널은 프로세스 내의 다른 스레드를 중단시키지 않고 계속 실행시켜준다. 단점으로는 유저스레드보다 느리다는게 단점이다.

#

멀티스레딩(Multi-threading)

  • 컴퓨터는 여러 개의 스레드를 효과적으로 실행할 수 있는 하드웨어를 지원한다.
  • 멀티스레딩은 프로세스 안에서 병렬 처리의 이점을 가질 수 있다.

###

장단점

  • 장점
    • 응답성
      • 대화형 프로그램을 멀티스레드하면, 프로그램 일부분(스레드)이 중단되거나 긴 작업을 수행하더라도 프로그램의 수행이 계속되어, 사용자에 대한 응답성이 증가된다. 예를 들어, 멀티스레드가 적용된 웹 브라우저 프로그램에서 하나의 스레드가 이미지 파일을 로드하고 있는 동안, 다른 스레드에서는 사용자의 다른 요청을 처리할 수 있다
    • 자원 공유
      • 스레드는 자동적으로 속한 프로세스의 자원(메모리)을 공유한다. 코드 공유의 이점은, 한 응용 프로그램이 같은 주소 공간 내에 여러 개의 다른 활동들을 하는 스레드를 가질 수 있다는 점이다
    • 경제성
      • 프로세스 생성에 메모리와 자원을 할당하는 것은 비용이 많이 든다. 스레드는 자신이 속한 프로세스의 자원들을 공유하기 때문에, 스래들을 생성하고 context switch를 하는 편이 보다 경제적이라고 할 수 있다
    • 멀티프로세서 활용
      • 멀티프로세서 구조에서는 각각의 스레드가 다른 프로세서에서 병렬로 수행될 수 있다. 단일 스레드 프로세스는 CPU가 많아도 CPU 한개에서만 실행된다. 즉, 다중 스레드화를 하면 다중CPU에서 병렬로 데이터를 처리할 수 있기때문에, 속도면에서도 좋다고 볼 수 있다
  • 단점
    • 다중 스레드는 캐시와 같은 하드웨어 리소스를 공유할 때 서로 간섭할 수 있기 때문
    • 하나의 스레드만 실행 중인 경우 싱글 스레드의 실행 시간이 개선되지 않고 오히려 지연될 수 있다
    • 멀티스레딩의 하드웨어 지원을 위해 응용 프로그램과 운영 체제 둘 다 충분한 변화가 필요하다
    • 스레드 스케줄링은 멀티스레딩의 주요 문제이기도 하다

#

멀티스레드 모델

  • 다-대-일(Many-to-One)
    • 여러 개의 사용자 수준 스레드들이 하나의 커널 스레드(프로세스)로 매핑되는 방식으로, 사용자 수준에서 스레드 관리가 이루어진다. 주로 커널 스레드를 지원하지 않는 시스템에서 사용하고, 한 번에 하나의 스레드만이 커널에 접근할 수 있다는 단점이 존재한다
  • 일-대-일(One-to-One)
    • 사용자 스레드들을 각각 하나의 커널 스레드로 매핑시키는 방식이다. 사용자 스레드가 생성되면 그에 따른 커널 스레드가 생성되는ㄱ ㅓㅅ이다. 이렇게 하면 다-대-일 방식에서 시스템 호출 시 다른 스레드들이 중단되는 문제를 해결할 수 있다. 하지만 커널 스레드도 한정된 자원을 사용하므로 무한정 생성할 수 없기 때문에 스레드를 생성할 때 그 개수를 염두에 두어야 한다
  • 다-대-다(Many-to-Many)
    • 여러 개의 사용자 스레드를 여러 개의 커널 스레드로 매핑시킨 모델이다. 다-대-일 방식과 일-대-일 방식의 문제점을 해결하기 위해 고안되었다. 커널 스레드는 생성된 사용자 스레드와 같은 수 또는 그 이하로 생성되어 스케줄링한다. 다-대-일 방식에서 스레드가 시스템 호출시 다른 스레드가 중단되는 현상과 일-대-일 방식에서 사용할 스레드의 수에 대해 고민하지 않아도 된다. 커널이 사용자 스레드와 커널 스레드의 매핑을 적절하게 조절한다

#

교착 상태 (dead lock)

  • 두 가지 이상의 작업이 서로 상대방의 작업을 끝나기를 하염없이 기다리고 있는 상태를 말한다
  • 철학자 과제에서 예를 들면 모든 철학자가 배가 고파서 fork를 들고 있다면 모든 철학자는 옆에 fork를 내려줄때까지 대기를 하게 된다. 하지만 모든 철학자들이 fork를 들고 있기 때문에 모두 fork를 들고 있는 상태에서 가만히 기다리게 된다. 이걸 보고 교착 상태(dead lock) 이라고 한다.

#

임계 영역(Critical Section)

  • 교착상태가 발생할 수 있는 영역을 말하며 이 과제에서는 fork에 해당한다
  • 교착상태가 발생하는 네가지 조건

    1. 상호 배제

      • 상호 배제(Mutual exclusion)
      • 스레드들이 필요로 하는 자원에 대해 배타적인 통제권을 요구하는 것

        쉽게 설명하면 어떤 스레드가 공유 자원을 사용하고 있다면 다른 스레드에서 접근할 수 없도록 통제하는 것을 뜻한다

    2. 점유 대기
    3. 비선점
    4. 순환 대기
    • 위의 4가지 조건 중 하나라도 만족하지 않는다면 교착상태가 발생하지 않는다.

#

Mutex vs Semaphore

  • Mutex란?

    • Mutual exclusion 의 철자를 따서 만들었다. 상호 배제를 뜻한다
    • 여러 스레드를 실행하는 환경에서 자원에 대한 접근제한을 강제하기 위한 동기화 매커니즘이다
    • 임계 영역에 스레드가 사용하고 있을 경우 다른 스레드가 공유 자원을 사용하려고 한다면 스레드를 blocking하고 대기큐에 sleep 시킨다
  • Semaphore란?

    • 멀티프로그래밍 환경에서 다수의 프로세스나 스레드의 여러 개의 공유 자원에 대한 접근을 제한하는 방법으로 사용한다

#

Philosoper 순서도

philo_순서도.png

#

philo에서 쓰는 함수들

##

1. mutex func

pthread_mutex_init

int pthread_mutex_init(pthread_mutex_t *mutex,
						const pthread_utex_atter *attr);
  • 설명
    • 첫 번째 인자는 mutex는 초기화 시킬 mutex를 말한다. 뮤텍스의 특징을 정의할 수 있는데, 이는 두 번째 인자인 attr를 통해 할 수 있다. 기본 뮤텍스 특징을 사용하려면 NULL을 이용하면 된다
    • mutex의 특징은 “fast”, “recurisev”, “error checking” 3가지 종류 중 하나를 선택할 수 있다. 기본적으로 “fast”가 사용된다
  • 반환값
    • 성공할 경우 0, 실패할 경우 -1 을 반환한다.

pthread_mutex_lock

  • 설명
    • mutex를 상호배제 하기 위해서 사용하는 함수로 매개변수로 던지는 위치의 mutex를 lock을 건다고 생각하면 쉽게 이해할 수 있다.
    • 잠금을 요청하고 만약 뮤텍스의 최근 상태가 unlocked라면 쓰레드는 잠금을 얻고 임계영역에 진입하게 된다
    • 다른 쓰레드가 뮤텍스 잠금을 얻은 상태라면 잠금을 얻을 수 있을 때가지 기다리게 된다

pthread_mutex_unlock

  • 설명
    • mutex의 상태가 lock인 경우에 그 잠금상태를 해제요청해주는 함수라고 할 수 있다

pthread_mutex_destroy

  • 설명
    • 지정된 mutex를 파괴한다

##

2. phtread func

pthread_create

int pthread_create(pthread_t *restrict thread,
								const pthread_attr_t *restrict attr,
								void *(*routine)(void *),
								void *restrict arg);
  • 설명

    1. pthread_t *restrict thread
      • 스레드의 식별자 스레드의 Id를 볼 수 있다
    2. const pthread_attr_T *restrict attr
      • 설정할 스레드 속성정보(보통은 NULL을 쓴다. NULL을 쓰면 기본 설정 적용이 된다)
    3. void (_**routine)(void _)
      • 스레드가 실행할 함수를 말한다
    4. void *restrict arg
      • 스레드가 실행할 함수에 들어갈 인자를 말한다
  • 함수 호출
    • pthread_create() 함수 호출 시 새 스레드가 생성되고, 생성된 스레드는 routine(arg)를 실행한다
    • 생성된 스레드는 다음 세가지 경우에 종료가 된다
      1. pthread_exit() 호출
      2. routine() 수행 후 return
      3. pthread_cancle() 호출
  • 반환값
    • 성공시 0을 반환하고 스레드의 식별자를 thread_t에 담는다
    • 오류 발생시 오류 번호를 반환하고 thread_t를 정의하지 않는다

pthread_join

int pthread_join(pthread_t thread, void **retval);
  • 설명

    1. pthread_t thread
      • 스레드의 식별자를 뜻한다
    2. void **retval
      • 스레드에 할당된 함수 실행 후 종료상태를 말한다
  • 함수 호출

    • pthread_join() 함수를 쓰면 지정된 스레드가 종료될때까지 기다린다. 스레드가 이미 종료된 경우에는 즉시 반환된다.
    • 대상 스레드의 종료 상태가 retval로 반환이 된다
  • 반환값

    • 성공시 0
    • 오류 발생시 오류번호반환

pthread_detach

int pthread_detach(pthread_t thread);
  • 설명

    • pthread_detach() 함수로 지정된 스레드는 분리된 것으로 표시된다. 해등 스레드가 종료되면 리소스가 자동으로 해제된다
  • 반환값

    • 성공시 0반환
    • 오류 발생 시 오류 번호 반환

C 카테고리 내 다른 글 보러가기