상세 컨텐츠

본문 제목

프로세스 vs 쓰레드, 뭐가 다르고 어떻게 활용될까??

CS지식 학습

by Tabris4547 2023. 11. 2. 18:12

본문

728x90

CS면접용으로 맨날 나오는 주제.

프로세스와 쓰레드의 차이는 뭔가요.

단순하게 "프로세스는 동작하는 프로그램을 의미하고

쓰레드는 프로세스의 작은 단위입니다"라고만 말하고 계신가요??

이번 시간에는 프로세스와 쓰레드, 서로 어떻게 다르고

어떤 식으로 활용될 수 있는지 살펴보겠습니다.

 

프로세스(Process)

 

먼저 프로세스는 '현재 수행하는 프로그램'이라고 이해하시면 되겠습니다.

우리가 메모리영역에 프로그램을 데이터로 저장했다면

이걸 실제로 동작시켜 현재 작동하는 것이

프로세스입니다.

프로세스 각각이 수행할 때마다

가상의 메모리가 만들어집니다.

이 가상메모리 공간은 해당 프로세스만을 위한 공간입니다.

모든 프로세스는 독립된 메모리공간을 사용하기 때문에

서로 프로세스 간에 영향을 미치지 않고 동작할 수 있습니다.

프로세스는 다음과 같이 트리구조를 형성하여

부모프로세스/자식프로세스 관계로 이루어져있습니다.

pid는 프로세스 고유번호를 의미합니다.

어떤 식으로 형성되는지 코드를 보면서 이야기하겠습니다.

 

 

프로세스 코드(C)

#include<sys/types.h>
#include<unistd.h>
#include<stdio.h>

int main(){

        int pid;

        pid=fork();
        if (pid==0)
                printf("child of %d is %d\n",getppid(),getpid());

        else
                printf("I am %d. My child is %d\n",getpid(),pid);

}

간단한 프로세스 코드입니다.

이 코드는 어떻게 동작할까요??

해당 코드의 결과값입니다.

 

코드를 해설하면

fork()를 통해 새로운 자식프로세스를 만듭니다.

자식프로세스는 fork()로 호출한 이후의 코드 전체를 복사합니다.

pid=0이면 자식프로세스고, 그렇지 않으면 부모프로세스입니다.

해당 결과값이 실행마다 다른 이유는

코드를 실행한 게 별개의 프로세스로 취급되어

실행할 때마다 프로세스 번호가 다릅니다.

 

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <assert.h>
#include <sys/wait.h>

int main(int argc, char *argv[]){
        if(argc != 2){
                fprintf(stderr, "Usage: ./sumfact number\n");
                exit(1);
        }

        int n = atoi(argv[1]);
        pid_t pid1, pid2;
		//먼저 합을 구한다
        if((pid1 = fork()) == 0){
                int i, sum = 0;
                for(i = 1; i <= n; i++)
                        sum += i;
                printf("sum: %d\n", sum);
                if(sum >= (1 << 8)) sum >>= 8;
                exit(sum); //구한 sum값을 리턴시킨다
        }
        else if(pid1 < 0){
                perror("while creating process 1");
                exit(1);
        }
        else{
        		//팩토리얼
                if((pid2 = fork()) == 0){
                        int i, fact = 1;
                        for(i = 2; i <= n; i++)
                                fact *= i;
                        printf("facttorial: %d\n", fact);
                        if(fact >= (1 << 8)) fact >>= 8;
                        exit(fact);//팩토리얼의 결과를 리턴
                }
                else if(pid2 < 0){
                        perror("while creating process 2");
                        exit(1);
								}
                else{
                        int sum, fact;
                        //waitpid를 통해 자식프로세스 종료를 기다린다
                        if(waitpid(pid1, &sum, 0) < 0){
                                perror("waitpid pid1");
                                exit(1);
                        }
                        if(waitpid(pid2, &fact, 0) < 0){
                                perror("waitpid pid2");
                                exit(1);
                        }
                        //두 가지 프로세스의 자식프로세스가 각각 종료되었는지 확인
                        assert(WIFEXITED(sum) && WIFEXITED(fact));
                        //sum과 fact에 각 자식 프로세스의 결과값을 입력
                        sum = WEXITSTATUS(sum);
                        fact = WEXITSTATUS(fact);
                        printf("sum + factorial = %d\n", sum + fact);
                        exit(0);
                }
        }
}

 

이 코드는 입력받은 숫자에 대해서

펙토리얼과 합을 모두 구한 후

각각을 더하는 프로그램입니다.

fact sum 두개의 프로그램을 만든 다음

각각의 프로세스가 종료될 때까지 기다린 다음

각각의 값을 구하는 프로그램입니다.

이를 위해, 각 프로세스간의 통신(process communication)이 필요하며

코드에서는 waitpid와 WEXITSTATUS를 통해 구현했습니다.

 

 

쓰레드

 

위처럼 프로세스를 사용하는 건 다음의 불편한 점이 따릅니다.

 

1. 굳이 모든 데이터를 다 복사해야하나??

->fork()처럼 자식프로세스로 같은 걸 복사한다면

그 안의 모든 것을 복사해야하는 부담이 있습니다.

위에서 fact 과 sum을 하는 프로그램을 보면

fact을 할 때는 fact에 대한 코드만 있으면 되고

sum을 할 때는 sum에 대한 코드만 있으면 되는데

굳이 비효율적으로 모든 코드를 복사해야할까??

만약 프로세스를 더 많이 생성한다면

그만큼 메모리를 많이 잡아먹고 속도가 느려지지 않을까?

 

2. 프로세스 통신...너무 어렵다.

->각각의 프로세스는 별개로 동작하기 때문에

각 프로세스 통신을 통해 데이터를 받아야합니다.

위의 코드를 보시면 느끼시겠지만

상당히 복잡합니다.

 

이런 문제점으로 등장한 개념이 바로 thread입니다.

쓰레드를 여러개 활용하는

멀티쓰레드를 표현한 그림입니다.

코드,데이터,파일 등 공통되는 부분을 냅두고

각각 별개의 레지스터,스택,pc를 활용합니다.

이 방법으로 공통이 되는 부분의 데이터를 공유해

불필요한 자원을 복사하지 않으며

프로세스처럼 통신하지 않아도 됩니다.

 

쓰레드 코드(C)

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


int sum=0;
int fa=1;

void su(int * num){
        sum+=(*num);
}

void fac(int *num){
        fa*=(*num);
}


int main(){
        pthread_t tid;
        pthread_t tid2;

        int N;
        scanf("%d",&N);

        for (int i=1;i<N;i++){
                pthread_create(&tid,NULL,su,&i);
                pthread_create(&tid2,NULL,fac,&i);
                pthread_join(tid,NULL);
                pthread_join(tid2,NULL);
        }
        printf("%d\n",sum);
        printf("%d\n",fa);
        printf("%d\n",sum+fa);

}

 

위의 fact sum프로그램을

쓰레드로 수정한 코드입니다.

프로세스 대비 코드가 간결해보입니다.

 

pthread_create를 통해 해당 쓰레드에 수행할 것을 생성합니다.

이후 pthread_join을 통해 해당 쓰레드를 수행합니다.

해당 코드에서는 for문을 돌 때마다

병렬적으로 쓰레드를 수행하여

전역변수의 sum과 fa의 값을 수정합니다.

 

 

쓰레드를 쓰는게 무조건 좋을까??

 

여기까지 읽으면 쓰레드가 훨씬 간편하고 효율적으로 보입니다.

어떤 자료에서도 "멀티쓰레드를 활용하는 것이 자원효율이 좋다"라는 게 있어

멀티프로세스는 안 좋게만 여겨졌습니다.

하지만 이는 상황마다 다릅니다.

 

안정성 문제

제가 간단하게 제작한 실시간 채팅프로그램으로

웹서버에서 클라이언트를 받을 때 

thread를 통해 사용자를 받았습니다.

여기서 사용자 하나를 강제종료시켰습니다.

이 경우, server에서 그동안의 메세지를 계속 내보내면서

동작의 에러가 발생합니다.

이후에 다시 접속하려고하면

서버가 이렇게 터저버리는 문제가 발생합니다.

이유가 무엇일지 분석해본 결과

클라이언트 강제종료로 인한 서버 에러였습니다.

각각의 클라이언트가 하나의 공통된 서버를 쓰기 때문에

하나의 클라이언트에 문제가 생긴 게

전체 서버까지 영향을 미치는거죠.

만약에 멀티프로세스로 통신했다면

클라이언트 하나가 문제생겨도

이러한 문제가 발생할 일이 없었을테죠.

 

동기화문제

 

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

int sum=0;
void  cal(void  *i){
        sum++;
        printf("%d\n",sum);
        return NULL;
}

int main(){
        pthread_t tid[10];

        for (int i=0;i<10;i++){

                pthread_create(&tid[i],NULL,cal,(void*)&i);
        }
        for (int j=0;j<10;j++){

                pthread_join(tid[j],NULL);

        }
        printf("sum: =%d\n",sum);
        return 0;
}

10개의 쓰레드를 만들어서

sum에 1을 더하는 코드입니다.

어떤 결과가 나올까요?

다행히 최종결과는 10으로 나오는데

중간 쓰레드에 입력된 값들이 각각 다르게 나오네요.

실제 이 코드를 몇번 돌리다보면

thread각각을 동작하는 시점에서의 sum값이

우리가 예상했던 1 2 3 4 5 6 7 8 9 10 이런 순서가 아닌

뒤죽박죽인 경우가 많습니다.

만약 thread에서 sum의 값에 따라 어떤 동작을 한다하면

그 결과가 동작할 때마다 다르기 때문에

동작을 예상할 수 없습니다.

이런 문제를 방지하기 위해

mutex등의 동기화 해결방법이 필요합니다.

이런 동기화에 대한 문제를 가지고 있어,

때로는 이런 문제없는 프로세스를 사용하는 것이 더 좋을때도 있습니다.

728x90

관련글 더보기

댓글 영역