728x90
반응형
 

C기반 I/O MultiThreading - 12. 멀티 프로세싱? 멀티 쓰레딩?

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

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
#include <stdio.h>
#include <unistd.h>
#include <pthread.h>
 
void* t_main(void *arg);
 
int main(void)
{
    pthread_t tid;
    int t_param = 8;
 
    if(pthread_create(&tid, NULL, t_main, (void *)&t_param)!=0// 3,4번째의 인자들의 자료형을 잘 맞춰주어야 한다.
    {
        printf("반환 값이 0이 아닌 값으로 pthread_create에 실패했습니다.\n");
        return -1;
    }
    sleep(10);
    printf("메인함수가 종료됩니다.\n");
 
    return 0;
}
 
void* t_main(void *arg)
{
    int i;
    int cnt = *((int*)arg);
 
    for(i=0; i<cnt; i++)
    {
        printf("thread_main이 실행중입니다\n");
        sleep(1);
    }
    return NULL;
}
cs

위의 예제를 실행한 결과이다. 메인 함수가 바로 종료되면 프로세스가 종료되기 때문에 그 내부의 실행 흐름인 쓰레드 또한 함께 바로 종료되기 때문에 메인 함수를 종료시키지 않고 붙잡아 둘 필요가 있었던 것이다. 그래서 17번 라인에서 sleep 함수로 10초를 버틴다!

위의 예제에 대한 실행 흐름을 그림으로 나타낸 것이다.

그런데 문제가 있다! 이전에 프로세스 이야기에서도 마찬가지였지만 sleep 함수를 통해서 프로그램의 실행 흐름을 대충 맞춘다는게 이게 말이나 되는 상황인가? 이러한 문제를 해결하기 위한 함수가 존재한다.

쓰레드가 종료할 때까지 대기! : pthread_join 함수의 등장 

첫 번째 인자로 넘어간 tid 값에 해당하는 쓰레드가 종료될 때 까지 main 함수는 대기를 한다!

코드를 보고 확인하겠다.

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
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>
 
void* t_main(void *arg);
 
int main(void)
{
    pthread_t tid;
    int t_param = 8;
    int * return_val = NULL;
 
    if(pthread_create(&tid, NULL, t_main, (void *)&t_param) != 0// 3,4번째의 인자들의 자료형을 잘 맞춰주어야 한다.
    {
        printf("반환 값이 0이 아닌 값으로 pthread_create에 실패했습니다.\n");
        return -1;
    }
 
    if(pthread_join(tid, (void **)&return_val) != 0)
    {
        printf("반환 값이 0이 아닌 값으로 pthread_join에 실패했습니다.\n");
        return -1;
    }
    printf("pthread_join 함수 호출 직 후\n");
    printf("쓰레드 메인 함수에서 반환된 값 : [%d] \n"*return_val);
    printf("메인함수가 종료됩니다.\n");
    free(return_val);
    return 0;
}
 
void* t_main(void *arg)
{
    int i;
    int cnt = *((int*)arg);
    int * thread_return_value = (int *)malloc(sizeof(int));
 
    printf("thread_main이 실행 되었습니다!\n");
    printf("반환 값 계산 후, 잠시 10초 대기하겠습니다ㅋㅋ - main에서 pthread_join의 효과를 보기 위함.\n");
 
    *thread_return_value = 100;
    *thread_return_value = (*thread_return_value) * cnt;
 
    sleep(10);
    return thread_return_value;
}
cs

중간 중간에 설명해야할 부분이 있는 것 같다 ㅋㅋ 포인터들이 좀 많이 깔려있는데 다른 예제들에 비해서. 근데 전혀. 어렵지 않다. 

위 코드에서는 그림에 해당하는 부분으로 설명이 될 것 같다.

다만, 이렇게 더블 포인터까지 써가면서 가리키는 이유는 쓰레드 간의 통신이기 때문에 공유 메모리 영역인 힙 영역의 메모리 공간에 값을 저장하고 또 그것을 외부에서 참조해서 쓰는 것이다. 아래는 실행 결과이다.

아래는 실행 흐름을 그림으로 나타낸 것이다.

 

쓰레드의 소멸

쓰레드는 꼭 직접 소멸을 해줘야한다! 쓰레드의 main 함수(위에서는 t_main)를 반환했다고 자동 소멸이 되는 것이 절대 아니다.

그러면 뭐 closesocket 같은 전용 close 형식의 함수가 존재하는가? 그렇다! 존재한다! 다만 close~ 형태는 아니고 이미 본 것도 존재한다. 바로 아래의 함수들이다.

첫 번째는 이미 봤던 pthread_join 함수이다. 이 함수를 호출하면 해당 tid의 쓰레드가 종료될 때까지 대기할뿐만 아니라 소멸까지도 책임지고 담당한다. 그러나 치명적인 단점은 말 그대로 main 함수를 대기시키는 블로킹에 빠진다는 점.

그래서 나온 것이 다음이다.

이 함수를 사용하면 쓰레드의 소멸까지 담당하여 처리되는데, 중요한 것은 블로킹에 빠지지 않는다는 것이다!

다음 예제를 통해서 보겠다.

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
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>
 
void* t_main(void *arg);
 
int main(void)
{
    pthread_t tid;
    int t_param = 8;
 
    int i = 0;
 
    if(pthread_create(&tid, NULL, t_main, (void *)&t_param) != 0// 3,4번째의 인자들의 자료형을 잘 맞춰주어야 한다.
    {
        printf("반환 값이 0이 아닌 값으로 pthread_create에 실패했습니다.\n");
        return -1;
    }
 
    printf("pthread_detach 함수 호출!\n");
    pthread_detach(tid); // tid 에 해당하는 쓰레드가 종료됨과 동시에 소멸시킨다.
 
    for(i=0; i<7; i++)
    {
        sleep(1);
        printf("쓰레드가 이미 종료되었지만, 블로킹 되지 않고 main만의 작업을 진행하다가 \n");
    }
    printf("main만의 작업 루틴 종료.\n");
    printf("메인함수가 종료됩니다.\n");
    return 0;
}
 
void* t_main(void *arg)
{
    int cnt = *((int*)arg);
 
    printf("thread_main이 실행 되었습니다!\n");
    printf("받은 값은 [%d]\n", cnt);
 
    printf("t_main 리턴 직전(종료라고 보면 됨)\n");
    return NULL;
}
cs

아래는 실행 결과로 블로킹이 없음을 확인할 수 있다.

위 예제에 대한 실행 흐름을 그림으로 나타내보았다.

 

쓰레드의 개념부터 생성 및 소멸까지 확인해보았다.

앞으로 남은 포스팅은 임계 영역에 대한 설명과 서버 프로그래밍이다. ㅋㅋ 제일 고비이다 고비..

728x90
반응형

+ Recent posts