반응형
728x90
반응형

멀티 프로세싱에 이어서, 멀티 프로세싱의 단점이 보완되는 멀티 쓰레딩 개념이다. 사실 단점이 보완되기는 하는데 함께 딸려오는 문제 거리도 만만치 않기 때문에 좀 상세히 볼 필요가 있다ㅋㅋ

아래는 멀티 프로세싱에 대한 포스팅의 시작이고, 그 아래에 있는 멀티 프로세싱 포스팅 중 제일 마지막 시리즈(?)에 프로세스 생성과 복사에 대한 개념이 조금 들어간다.

 

C기반 I/O Multiprocessing - 8. 드디어 올리는 멀티 프로세싱

진짜 다 까먹어서 다시 예제 쳐보고 다시 올린다... 정말 미리미리 올리고 자주자주 봐야합니다.. 포스팅 순서가 사실 멀티 프로세싱이 먼저이지만, 게임 서버 개발에 사용한 것은 멀티 플렉싱

typingdog.tistory.com

 

 

C기반 I/O Multiprocessing - 11. 프로세스 정리 및 IPC

C기반 I/O Multiprocessing - 8. 드디어 올리는 멀티 프로세싱 진짜 다 까먹어서 다시 예제 쳐보고 다시 올린다... 정말 미리미리 올리고 자주자주 봐야합니다.. 포스팅 순서가 사실 멀티 프로세싱이 먼

typingdog.tistory.com

링크 올리면서 발견한건데.. 예제 쳐보고가 아니라 예제를 '타이핑'해보고...ㅋㅋㅋㅋ

멀티 프로세스의 특징과 단점

자, 일단은 멀티 프로세싱의 단점을 확인할 필요가 있다. 왜냐하면 멀티 쓰레딩멀티 프로세싱과 유사한 기능을 제공하지만 단점을 보완할 수 있는 개념이기 때문이다. 멀티 프로세스의 장점은 멀티 쓰레딩의 단점을 통해 확인해야하기 때문에 지금 이 포스팅에서는 언급하지 않겠다.

멀티 쓰레드는 겉에서 봤을 때에는 멀티 프로세스와 비스므리한 개념이다.
멀티 프로세스의 특징을 간단하게 다시 살펴보자.

-멀티 프로세스의 특징

1. 프로세스의 복사는 사실 프로세스 생성을 거친다.
2. 프로세스의 복사는 스택, 데이터, 힙 영역이 모두 복사되고, 부모와 자식은 서로 독립적인 메모리 공간을 갖는다.
3. 프로세스의 복사는 파일 디스크립터 복사로 인해 파일 디스크립터와 소켓의 연결 관계를 n:1로 만든다
4. 프로세스의 복사는 부모 프로세스와 자식 프로세스로 나뉘며, 부모의 프로세스가 자식의 프로세스 반환을 꼭 회수해야한다.

말 그대로 복사이기 때문에 위와 같은 현상들이 일어나는데, 이러한 특징들로 인해서 멀티 프로세스의 단점은 다음과 같다. 위의 멀티 프로세스의 특징에 대응되도록 색을 칠했다.

-멀티 프로세스의 단점

1. 프로세스 복사 과정은 시스템에 굉장히 부담스럽다. (프로세스의 생성 자체가 부담스럽다)
2. 프로세스는 서로 별개의 요소이기 때문에 별개의 독립된 메모리 공간을 갖고, 이로 인해서 프로세스 간에 통신이 어렵다 ( 사실 이건 장점일수도?ㅋㅋ )
3. 파일 디스크립터와 소켓의 연결 관계가 n:1일 경우, 소켓에 해당하는 모든 파일 디스크립터를 종료해야 비로소 소켓이 종료된다.
4. 프로세스의 복사는 부모 프로세스와 자식 프로세스로 나뉘는데, 이로 인해서 자식 프로세스의 반환 값 회수가 이루어지지 않으면 좀비 프로세스가 발생한다.
5. CPU가 프로세스 별로 시간을 할당해서 여러 프로세스를 처리하는 시분할 처리를 하기 위해 현재 처리할 프로세스를 메모리에 올리고, 현재 처리하지 않는 프로세스를 메모리에 내리는 작업을 PCB를 이용하여 행하는데 이를 컨텍스트 스위칭이라는 과정이다. 이러한 과정은 하드 디스크 레벨의 I/O 까지 연계되기 때문에 부담이 굉장히 크다.

-멀티 쓰레드는 멀티 프로세스의 어떤 부분들이 어떻게 커버되었을까?

단점이 생각보다 많다ㅋㅋㅋㅋㅋ 멀티 쓰레드는 멀티 프로세스의 단점을 커버하기 위해서 등장했다. ( 물론 전적으로 그렇지는 않겠다만ㅋ... ) 마찬가지로 위의 단점들에 대응되도록 색을 칠했다.

1. 쓰레드는 복사하는 과정이 아니며, 쓰레드의 생성과 쓰레드의 컨텍스트 스위칭은 프로세스의 그것들보다 빠르다.
2. 쓰레드는 메모리 영역을 통째로 복사하지 않고, 일부를 공유하며(힙, 데이터), 병렬 흐름에 필요한 부분(스택)만 독립적으로 갖는다. 그렇기 때문에 공유되는 영역을 통해 쓰레드 간의 통신이 가능하다.
3. 쓰레드의 생성은 프로세스처럼 통째로 복사하는 과정이 아니기 때문에 해당하지 않는다. 프로세스에는 File Descriptor table을 갖는데, 프로세스가 복사되면 이 테이블 자체가 복사된다. 그렇기 때문에 문제가 되는 것이고, 파일 디스크립터의 복사는 단순히 파일 디스크립터 값을 다른 변수로 넘겨주네 마네 하는 차원이 아니라는 소리다.
4. 프로세스처럼 통째로 복사되는 과정이 아니기 때문에 해당하지 않는다.
5. 쓰레드의 컨텍스트 스위칭은 쓰레드 라이브러리에 의해 TCB(Thread Controll Block) 을 참조하여 발생하며, OS 스케줄러에 의해 PCB를 참조하여 컨텍스트 스위칭되는 프로세스의 과정보다는 훨씬 빠르다.

-이런 멀티 프로세스의 단점을 커버하는 멀티 쓰레드(쓰레드)는 무엇인가?

위는 프로세스에 대한 포스팅에서 간단하게 정리한 정보인데, 프로세스와 비교하면서 보면 더 와닿을 것 같아서 다시 올렸다. 

프로세스 관점에서 별도의 실행 흐름을 구성하는 단위.
생성될 때, 프로세스와 마찬가지로 TCB(Thread Controll Block) 라고 하는 쓰레드에 대한 정보 구조가 생성된다.

쓰레드는 프로세스의 일부이며 실행의 단위이기 때문에 TCB에는 PCB만큼의 많은 정보가 저장되지는 않는다.
또한, 프로세스의 일부이기 때문에 프로세스가 할당 받은 메모리의 공간을 공유할 수 있다. (통신에 용이)

-그렇다면 멀티 쓰레드란 무엇일까?

히..힘들었..다..

프로세스 내에 여러 쓰레드를 운용하여 병렬 처리하는 방법을 멀티 쓰레드라고 한다.

개념적인 부분은 이 정도로 마무리 하고 다음 포스팅에서는 쓰레드 생성 및 소멸 등의 과정을 코드로 볼 것이다.

728x90
반응형
728x90
반응형
 

C기반 I/O Multiprocessing - 8. 드디어 올리는 멀티 프로세싱

진짜 다 까먹어서 다시 예제 쳐보고 다시 올린다... 정말 미리미리 올리고 자주자주 봐야합니다.. 포스팅 순서가 사실 멀티 프로세싱이 먼저이지만, 게임 서버 개발에 사용한 것은 멀티 플렉싱

typingdog.tistory.com

 

 

 

C기반 I/O Multiprocessing - 9. 멀티 프로세싱에서 좀비 문제

C기반 I/O Multiprocessing - 8. 드디어 올리는 멀티 프로세싱 진짜 다 까먹어서 다시 예제 쳐보고 다시 올린다... 정말 미리미리 올리고 자주자주 봐야합니다.. 포스팅 순서가 사실 멀티 프로세싱이 먼

typingdog.tistory.com

이전 시리즈(?) 들이다.

이전 내용들을 토대로 멀티 프로세싱 서버를 구현해볼 것이다. 간단하게 특징있는 부분만 짚어본다.

시그널을 등록하는 코드.

파일 디스크립터와 소켓의 관계를 보여준다.

프로세스가 복제가 되면 서버 소켓에 해당하는 파일 디스크립터도 복제가 되고, 클라이언트 소켓에 해당하는 파일 디스크립터도 복제가 된다. 그렇지만 파일 디스크립터는 프로세스의 소유이고, 소켓을 가리키는 숫자에 불과하기 때문에 복제가 되도 상관없지만 소켓 같은 경우는 운영체제 소유이기 때문에 복제가 되지 않고, 복제 되어서도 안된다. 

그렇지만 소켓을 제거하려면 해당 소켓을 동일하게 가리키는 파일 디스크립터를 모두 제거해야한다. 부모든 자식이든. 그렇기 때문에 각 부모, 자식에서는 필요한 디스크립터만 살려두고 나머지를 제거해야 나중에 잊지 않고 완벽하게 소켓을 제거할 수 있다.

전체 코드 및 실행 결과

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
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
 
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <signal.h>
#include <sys/wait.h>
#include <arpa/inet.h>
#include <sys/socket.h>
 
#define BUFSIZE 30
 
void ErrorMsg(const char *msg);
void GetChildReturnValue(int sig);
 
int main(void)
{
    int serv_sock, clnt_sock;
    struct sockaddr_in serv_addr, clnt_addr;
 
    int first_clnt_sock = -1, last_clnt_sock = -1, i = 0;
 
    pid_t pid;
    struct sigaction act;
 
    socklen_t addr_size;
    int strLen, state;
 
    char buf[BUFSIZE];
 
    act.sa_handler = GetChildReturnValue;
    sigemptyset(&act.sa_mask);
    act.sa_flags = 0;
    state = sigaction(SIGCHLD, &act, 0);
 
    serv_sock = socket(PF_INET,SOCK_STREAM,0);
    if(serv_sock == -1)
        ErrorMsg("socket() error");
 
    memset(&serv_addr,0,sizeof(serv_addr));
    serv_addr.sin_family = AF_INET;
    serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);
    serv_addr.sin_port = htons(7010);
 
    if(bind(serv_sock, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) == -1)
        ErrorMsg("bind() error");
 
    if(listen(serv_sock, 5== -1)
        ErrorMsg("listen() error");
 
    while(1)
    {
        addr_size = sizeof(clnt_addr);
        clnt_sock = accept(serv_sock, (struct sockaddr *)&clnt_addr, &addr_size);
        if(clnt_sock == -1)
            continue;
        else
            printf("새로운 클라이언트가 접속 [%d]\n", clnt_sock);
 
        // 서버 측에서 클라이언트 소켓을 일괄 제거 하기 위함.
        // 리눅스에서는 파일 디스크립터가 1씩 증가함을 전제로 작성됨.
        // 아래 로직 영역이 없으면 클라이언트 디스크립터는 항상 같은 값을 나타냄..
        if(first_clnt_sock == -1)
            first_clnt_sock = clnt_sock; // 처음 디스크립터 값만 기록함.
        last_clnt_sock = clnt_sock; // 계속 갱신.
 
        pid = fork();
        /*
            이 때, 프로세스가 복사되면서 파일 디스크립터 값 또한 복사가 된다. 다만 소켓은 복사 되지 않는다.
            그 이유는 소켓은 운영체제 소유, 파일 디스크립터는 프로세스의 소유.
 
            하나의 소켓에 두 개의 파일 디스크립터가 존재하는 경우에는 두 개의 파일 디스크립터가 모두
            소멸되어야 소켓이 비로소 소멸된다.
 
            -> 그렇기 때문에 꼭 서로에게 상관 없는 소켓의 파일 디스크립터는 꼭 닫아줘야 한다.
            -> 부모 프로세스의 영역에서 위 라인에서 얻은 클라이언트 소켓을 꼭 닫아준다.
        */
        if(pid == -1// 프로세스 생성이 제대로 안 됐을 경우에는
        {
            close(clnt_sock); // 연결을 끊어버림. 왜냐하면 프로세스 생성이 제대로 안됐기 때문에.
            continue// 또 다른 접속을 받기 위함.
        }
 
        if(pid == 0// 자식 프로세스 실행 영역
        {
            close(serv_sock);
            // 자식 프로세스에서는 서버 소켓은 필요 없다. 연결된 클라이언트에 대해서만 처리하면 되기 때문.
            // 하나의 소켓, 둘의 파일 디스크립터이므로 꼭 닫아줘야한다.
            while((strLen = read(clnt_sock, buf, BUFSIZE)) != 0)
            {
                printf("[%d]로부터 온 메시지 : [%s]\n", clnt_sock, buf);
                write(clnt_sock, buf, strLen); // 내용을 다시 보냄. 에코.
            }
 
            close(clnt_sock);
            printf("클라이언트 종료 : [%d]\n", clnt_sock);
 
            return 0// 자식 클라이언트에서의 인자 전달이 이루어짐.
        }
        else // 부모 프로세스 실행 영역
        {
            // 부모 실행 영역에서는 실행할 로직이 없다.
        }
    }
    // 안타깝게도 이를 실행할 수 있는 방법은 현재까지 공부한 내용으로는 없다.
    // 부모 프로세스에서 쓰레드 등을 이용해서 위의 while(1)을 탈출할 수 있도록 해야한다.
 
    for( i = first_clnt_sock; i <= last_clnt_sock; i++)
        close(i);
 
    return 0;
}
 
void GetChildReturnValue(int sig)
{
    int status;
    pid_t pid = waitpid(-1&status, WNOHANG);
    if(WIFEXITED(status))
    {
        printf("종료 처리된 자식 프로세스가 소멸되었습니다.[pid:%d][return:%d]\n", pid, WEXITSTATUS(status));
    }
    return;
}
 
void ErrorMsg(const char *msg)
{
    fputs(msg, stderr);
    fputc('\n',stderr);
    exit(1);
}
 
cs

728x90
반응형

+ Recent posts