철학자 - thread, process

JaeGu Jeong·2022년 2월 15일
0

Linux

목록 보기
2/3
post-thumbnail

about mutex, semaphore, thread, process...

프로세스 (Process)

프로세스는 독립된 Code, Data, Stack, Heap메모리를 할당 받는다.

Code : 작성한 코드가 담긴 영역
Data : 전역변수, 정적변수
Stack : LIFO로 함수가 쌓이고 사용이 끝난 함수는 인출되는 영역
Heap : 동적할당 된 메모리를 수동으로 관리 할 수 있는 영역

운영체제는 안정성을 위해 프로세스 밖의 메모리 접근을 금지한다. 만약 임의로 접근시 Segmentation Fault가 발생한다. 만약 다른 프로세스와 통신하고 싶다면 Inter-Process Communication을 사용해야 하는데 방식은 파이프, 시그널 등이 있다. 멀티 프로세스 사용시 프로세스 하나가 문제가 발생해도 다른 프로세스에 영향을 주지는 않지만, 메모리 공유가 안되므로 멀티 스레드보다 context switching이 느린 단점이 있다.

파이프와 시그널로 프로세스가 통신하는 코드 예시 (아래)

스레드 (Thread)

스레드는 프로세스 안에서 실행되는 흐름의 단위이다.

각각의 스레드는 고유의 스택을 가지고 code, data, heap을 공유한다.

멀티스레드는 음악회에서 한 사람이 바이올린과 피아노를 빠르게 바꿔가면서 연주하는 것과 같다. 이 속도는 너무 빨라서 인간이 느끼기에 동시에 움직인다고 느껴진다. 하지만 바이올린 솔로를 연주하는 상황에서는 오히려 싱글스레드가 낫다. 아무리 컴퓨터가 빨라도 스위칭시간(context switching)이 있기때문이다.
멀티스레드는 대표적으로 웹서버가 사용한다. 스레드끼리 메모리를 공유하기 때문에 여러가지 작업을 빠르게 처리하리 할 수 있기 때문이다.

멀티 스레드도 단점이 존재한다. 스레드끼리 자원을 공유하기때문에 동기화문제가 발생한다.

#include <pthread.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>

int g_resource = 0;  //스레드 공유자원

// 스레드 공유자원을 +1씩 3번하는 함수
void *t_function(void *data)
{
    int i;
    char* thread_name = (char*)data;

    g_resource = 0;
    for (i = 0; i < 3; i++)
    {
        printf("%s COUNT %d\n", thread_name, g_resource);
        g_resource++;
        usleep(100);
    }
}

int main()
{
    pthread_t p_thread1; //스레드 1
    pthread_t p_thread2; //스레드 2
    int status;
 
    pthread_create(&p_thread1, NULL, t_function, (void *)"Thread1"); //스레드1 생성
    pthread_create(&p_thread2, NULL, t_function, (void *)"Thread2"); //스레드2 생성
    while (1)
        ;
}

본래 의도는 각 스레드가 0 1 2를 출력하는 것이지만 실행하면 아래처럼 나온다.

위와 같은 문제를 해결하기 위해서 뮤텍스를 사용한다.

#include <pthread.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>

pthread_mutex_t mutex_lock; //뮤텍스 선언
 
int g_resource = 0; //스레드 공유자원
 
void *t_function(void *data)
{
    int i;
    char* thread_name = (char*)data;

    pthread_mutex_lock(&mutex_lock); //뮤텍스 락 설정 (다른 스레드는 뮤텍스 해제까지 대기)
    g_resource = 0;
    for (i = 0; i < 3; i++)
    {
        printf("%s COUNT %d\n", thread_name, g_resource);
        g_resource++;
    }
    pthread_mutex_unlock(&mutex_lock); //뮤텍스 락을 해제
}
int main()
{
    pthread_t p_thread1, p_thread2;
    int status;

    pthread_mutex_init(&mutex_lock, NULL); //뮤텍스 기본옵션으로 초기화
    pthread_create(&p_thread1, NULL, t_function, (void *)"Thread1"); //스레드 1
    pthread_create(&p_thread2, NULL, t_function, (void *)"Thread2"); //스레드 2
    while (1)
        ;
}

하지만 스레드끼리 뮤텍스를 이용하다가 해제조건을 서로 막는경우 교착상태(Deadlock)가 발생하는데, 이것이 싱글스레드보다 설계가 어려운 이유이다.

이러한 교착상태를 설명하는 문제가 대표적인 철학자 문제(philosophers problem)이다.

1. 왼쪽 포크가 사용 가능해질 때까지 대기한다. 만약 사용 가능하다면 집어든다.
2. 오른쪽 포크가 사용 가능해질 때까지 대기한다. 만약 사용 가능하다면 집어든다.
3. 양쪽의 포크를 잡으면 일정 시간만큼 식사를 한다.
4. 오른쪽 포크를 내려놓는다.
5. 왼쪽 포크를 내려놓는다.
6. 일정 시간 생각을 한다.
7. 다시 1번으로 돌아간다.
*** 철학자가 식사후에 일정시간안에 다시 식사를 하지 못하면 죽음 처리한다.

만약 설계가 부실하면 모든 철학자가 왼쪽 포크를 집고, 아무도 오른쪽포크를 잡지못해서 철학자가 죽음을 맞이 할 것이다.

이 문제의 교착상태 해결방법은 적절한 usleep으로 짝수번째 철학자와 홀수번째 철학자가 번갈아서 식사하도록 설계하는 것이 핵심이다.

프로세스와 세마포어 version

*** 모든 포크는 중앙에 있다.
1. 포크가 사용 가능해질 때까지 대기한다. 만약 사용 가능하다면 집어든다.
2. 포크가 사용 가능해질 때까지 대기한다. 만약 사용 가능하다면 집어든다.
3. 양손에 포크를 잡으면 일정 시간만큼 식사를 한다.
4. 포크를 내려놓는다.
5. 포크를 내려놓는다.
6. 일정 시간 생각을 한다.
7. 다시 1번으로 돌아간다.
*** 철학자가 식사후에 일정시간안에 다시 식사를 하지 못하면 죽음 처리한다.

이번엔 철학자가 프로세스일 때 뮤텍스대신 세마포어로 락을 거는 문제인다. 이번엔 메모리가 공유되지 않지만 세마포어를 사용하면 전역변수처럼 프로세스들이 상호배제가 가능하다. 뮤텍스는 각각의 포크가 정확하게 반납되어야 그 포크를 사용 할 수있지만, 세마포어는 갯수만 남아있다면 사용가능하다.

profile
BackEnd Developer

0개의 댓글